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

import cn.novalon.gym.manage.common.util.RedisUtil;
import cn.novalon.gym.manage.member.entity.MemberCard;
import cn.novalon.gym.manage.member.entity.MemberCardRecord;
import cn.novalon.gym.manage.member.entity.MemberCardTransaction;
import cn.novalon.gym.manage.member.enums.CardEvent;
import cn.novalon.gym.manage.member.enums.MemberCardRecordStatus;
import cn.novalon.gym.manage.member.enums.MemberCardType;
import cn.novalon.gym.manage.member.enums.TransactionType;
import cn.novalon.gym.manage.member.handler.DistributedLockService;
import cn.novalon.gym.manage.member.handler.ExpirationReminderService;
import cn.novalon.gym.manage.member.handler.MemberCardStateMachine;
import cn.novalon.gym.manage.member.handler.RefundSagaHandler;
import cn.novalon.gym.manage.member.repository.MemberCardRecordRepository;
import cn.novalon.gym.manage.member.repository.MemberCardRepository;
import cn.novalon.gym.manage.member.service.IMemberCardService;
import cn.novalon.gym.manage.member.service.IMemberCardTransactionService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.time.LocalDateTime;

/**
 * 会员卡服务实现
 * 
 * @author 付嘉
 * @date 2026-05-27
 */
@Slf4j
@Service
public class MemberCardServiceImpl implements IMemberCardService {
    private final MemberCardRepository memberCardRepository;
    private final MemberCardRecordRepository recordRepository;
    private final IMemberCardTransactionService transactionService;
    private final MemberCardStateMachine stateMachine;
    private final DistributedLockService distributedLockService;
    private final ExpirationReminderService expirationReminderService;
    private final RefundSagaHandler refundSagaHandler;
    private final RedisUtil redisUtil;

    private static final String MEMBER_CARD_CACHE_PREFIX = "member:card:";
    private static final long CACHE_EXPIRE_SECONDS = 300;

    public MemberCardServiceImpl(MemberCardRepository memberCardRepository,
                                 MemberCardRecordRepository recordRepository,
                                 IMemberCardTransactionService transactionService,
                                 MemberCardStateMachine stateMachine,
                                 DistributedLockService distributedLockService,
                                 ExpirationReminderService expirationReminderService,
                                 RefundSagaHandler refundSagaHandler,
                                 RedisUtil redisUtil) {
        this.memberCardRepository = memberCardRepository;
        this.recordRepository = recordRepository;
        this.transactionService = transactionService;
        this.stateMachine = stateMachine;
        this.distributedLockService = distributedLockService;
        this.expirationReminderService = expirationReminderService;
        this.refundSagaHandler = refundSagaHandler;
        this.redisUtil = redisUtil;
    }

    @Override
    public Mono<MemberCard> findByMemberCardIdAndDeletedAtIsNull(Long memberCardId) {
        String cacheKey = MEMBER_CARD_CACHE_PREFIX + memberCardId;
        Object cached = redisUtil.get(cacheKey);
        if (cached != null && cached instanceof MemberCard) {
            log.debug("从缓存获取会员卡信息, memberCardId: {}", memberCardId);
            return Mono.just((MemberCard) cached);
        }

        return memberCardRepository.findByMemberCardIdAndDeletedAtIsNull(memberCardId)
                .doOnSuccess(card -> {
                    if (card != null) {
                        redisUtil.setWithExpire(cacheKey, card, CACHE_EXPIRE_SECONDS);
                    }
                });
    }

    @Override
    public Flux<MemberCard> findWithConditions(Integer status, String name, String type,
                                               Double minPrice, Double maxPrice, Pageable pageable) {
        return memberCardRepository.findWithConditions(status, name, type, minPrice, maxPrice, pageable);
    }

    @Override
    public Mono<Long> countWithConditions(Integer status, String name, String type,
                                          Double minPrice, Double maxPrice) {
        return memberCardRepository.countWithConditions(status, name, type, minPrice, maxPrice);
    }

    @Override
    public Flux<MemberCard> findByMemberCardStatusAndDeletedAtIsNull(Integer status, Pageable pageable) {
        return memberCardRepository.findByMemberCardStatusAndDeletedAtIsNull(status, pageable);
    }

    @Override
    public Mono<Boolean> existsPurchasedRecord(Long memberCardId) {
        return memberCardRepository.existsPurchasedRecord(memberCardId);
    }

    @Override
    public Mono<Integer> logicalDelete(Long memberCardId) {
        return memberCardRepository.logicalDelete(memberCardId);
    }

    @Override
    public Flux<MemberCard> findActiveCards(Integer status) {
        return memberCardRepository.findActiveCards(status);
    }

    @Override
    public Mono<MemberCard> save(MemberCard entity) {
        return memberCardRepository.save(entity)
                .doOnSuccess(saved -> {
                    if (saved.getMemberCardId() != null) {
                        clearCardCache(saved.getMemberCardId());
                    }
                });
    }

    @Override
    public Mono<MemberCardRecord> purchaseCard(Long memberId, Long memberCardId, Long sourceOrderId) {
        return memberCardRepository.findByMemberCardIdAndDeletedAtIsNull(memberCardId)
                .switchIfEmpty(Mono.error(new RuntimeException("会员卡类型不存在")))
                .flatMap(card -> {
                    if (card.getMemberCardStatus() != null && card.getMemberCardStatus() == 1) {
                        return Mono.error(new RuntimeException("该会员卡已禁用"));
                    }

                    MemberCardType cardType = MemberCardType.valueOf(card.getMemberCardType());

                    return distributedLockService.executeWithLock(
                            memberId.toString(),
                            cardType.name(),
                            Mono.defer(() -> createCardRecord(memberId, memberCardId, sourceOrderId, card))
                    );
                })
                .flatMap(record -> createTransaction(record, TransactionType.PURCHASE, "购买会员卡")
                        .thenReturn(record))
                .flatMap(record -> expirationReminderService.scheduleExpirationReminder(record)
                        .then(Mono.just(record)));
    }

    private Mono<MemberCardRecord> createCardRecord(Long memberId, Long memberCardId, 
                                                      Long sourceOrderId, MemberCard card) {
        return Mono.defer(() -> {
            MemberCardRecord record = MemberCardRecord.builder()
                    .memberId(memberId)
                    .memberCardId(memberCardId)
                    .sourceOrderId(sourceOrderId)
                    .purchaseTime(LocalDateTime.now())
                    .status(MemberCardRecordStatus.ACTIVE)
                    .build();

            MemberCardType cardType = MemberCardType.valueOf(card.getMemberCardType());
            LocalDateTime now = LocalDateTime.now();

            switch (cardType) {
                case TIME_CARD:
                    record.setExpireTime(now.plusDays(card.getMemberCardValidityDays()));
                    record.setRemainingTimes(0);
                    record.setRemainingAmount(0.0);
                    break;
                case COUNT_CARD:
                    record.setExpireTime(now.plusDays(card.getMemberCardValidityDays()));
                    record.setRemainingTimes(card.getMemberCardTotalTimes());
                    record.setRemainingAmount(0.0);
                    break;
                case STORED_VALUE_CARD:
                    record.setExpireTime(now.plusYears(1));
                    record.setRemainingTimes(0);
                    record.setRemainingAmount(card.getMemberCardAmount());
                    break;
                default:
                    return Mono.error(new RuntimeException("不支持的会员卡类型"));
            }

            return recordRepository.insertActiveRecord(
                    record.getMemberId(),
                    record.getMemberCardId(),
                    record.getExpireTime(),
                    record.getRemainingTimes(),
                    record.getRemainingAmount(),
                    record.getSourceOrderId()
            );
        });
    }

    @Override
    public Mono<MemberCardRecord> renewCard(Long recordId, Integer addTimes, Double addAmount, 
                                             Integer addDays, Long sourceOrderId) {
        return recordRepository.findById(recordId)
                .switchIfEmpty(Mono.error(new RuntimeException("会员卡记录不存在")))
                .flatMap(originalRecord -> stateMachine.validateTransition(originalRecord, CardEvent.RENEW)
                        .then(Mono.just(originalRecord)))
                .flatMap(originalRecord -> memberCardRepository.findByMemberCardIdAndDeletedAtIsNull(originalRecord.getMemberCardId())
                        .switchIfEmpty(Mono.error(new RuntimeException("会员卡类型不存在")))
                        .flatMap(card -> {
                            MemberCardType cardType = MemberCardType.valueOf(card.getMemberCardType());

                            return distributedLockService.executeWithLock(
                                    originalRecord.getMemberId().toString(),
                                    cardType.name(),
                                    Mono.defer(() -> doRenewCard(originalRecord, card, addTimes, addAmount, addDays))
                            );
                        }));
    }

     private Mono<MemberCardRecord> doRenewCard(MemberCardRecord record, MemberCard card, 
                                                Integer addTimes, Double addAmount, Integer addDays) {
        LocalDateTime now = LocalDateTime.now();
        MemberCardType cardType = MemberCardType.valueOf(card.getMemberCardType());

        switch (cardType) {
            case TIME_CARD:
                LocalDateTime currentExpire = record.getExpireTime();
                LocalDateTime baseTime = (currentExpire != null && currentExpire.isAfter(now)) ? currentExpire : now;
                int daysToAdd = addDays != null ? addDays : card.getMemberCardValidityDays();
                record.setExpireTime(baseTime.plusDays(daysToAdd));
                break;
            case COUNT_CARD:
                int currentTimes = record.getRemainingTimes() != null ? record.getRemainingTimes() : 0;
                int timesToAdd = addTimes != null ? addTimes : card.getMemberCardTotalTimes();
                record.setRemainingTimes(currentTimes + timesToAdd);
                if (MemberCardRecordStatus.USED_UP.equals(record.getStatus())) {
                    record.setStatus(MemberCardRecordStatus.ACTIVE);
                }
                break;
            case STORED_VALUE_CARD:
                double currentAmount = record.getRemainingAmount() != null ? record.getRemainingAmount() : 0.0;
                double amountToAdd = addAmount != null ? addAmount : card.getMemberCardAmount();
                record.setRemainingAmount(currentAmount + amountToAdd);
                if (MemberCardRecordStatus.USED_UP.equals(record.getStatus())) {
                    record.setStatus(MemberCardRecordStatus.ACTIVE);
                }
                break;
            default:
                return Mono.error(new RuntimeException("不支持的会员卡类型"));
        }

        return recordRepository.save(record)
                .flatMap(updatedRecord -> createTransaction(updatedRecord, TransactionType.RENEW, "续费会员卡")
                        .then(expirationReminderService.scheduleExpirationReminder(updatedRecord))
                        .thenReturn(updatedRecord));
    }

    @Override
    public Mono<MemberCardRecord> useCard(Long recordId, Integer deductTimes, Double deductAmount) {
        return recordRepository.findById(recordId)
                .switchIfEmpty(Mono.error(new RuntimeException("会员卡记录不存在")))
                .flatMap(record -> stateMachine.validateTransition(record, CardEvent.USE)
                        .then(Mono.just(record)))
                .flatMap(record -> memberCardRepository.findByMemberCardIdAndDeletedAtIsNull(record.getMemberCardId())
                        .switchIfEmpty(Mono.error(new RuntimeException("会员卡类型不存在")))
                        .flatMap(card -> {
                            MemberCardType cardType = MemberCardType.valueOf(card.getMemberCardType());

                            return distributedLockService.executeWithLock(
                                record.getMemberId().toString(),
                                cardType.name(),
                                Mono.defer(() -> doUseCard(record, card, deductTimes, deductAmount))
                            );
                        }));
    }

    private Mono<MemberCardRecord> doUseCard(MemberCardRecord record, MemberCard card, 
                                              Integer deductTimes, Double deductAmount) {
        if (!MemberCardRecordStatus.ACTIVE.name().equals(record.getStatus())) {
            return Mono.error(new RuntimeException("会员卡状态不正确"));
        }

        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("会员卡已过期"));
                }
                break;
            case COUNT_CARD:
                int currentTimes = record.getRemainingTimes() != null ? record.getRemainingTimes() : 0;
                int timesToDeduct = deductTimes != null ? deductTimes : 1;
                if (currentTimes < timesToDeduct) {
                    return Mono.error(new RuntimeException("剩余次数不足"));
                }
                record.setRemainingTimes(currentTimes - timesToDeduct);
                if (record.getRemainingTimes() == 0) {
                    record.setStatus(MemberCardRecordStatus.USED_UP);
                }
                break;
            case STORED_VALUE_CARD:
                double currentAmount = record.getRemainingAmount() != null ? record.getRemainingAmount() : 0.0;
                double amountToDeduct = deductAmount != null ? deductAmount : 0.0;
                if (currentAmount < amountToDeduct) {
                    return Mono.error(new RuntimeException("余额不足"));
                }
                record.setRemainingAmount(currentAmount - amountToDeduct);
                if (record.getRemainingAmount() == 0) {
                    record.setStatus(MemberCardRecordStatus.USED_UP);
                }
                break;
            default:
                return Mono.error(new RuntimeException("不支持的会员卡类型"));
        }

        return recordRepository.save(record)
                .flatMap(updatedRecord -> createTransaction(updatedRecord, TransactionType.DEDUCT, "使用会员卡")
                        .thenReturn(updatedRecord));
    }

    @Override
    public Mono<Void> refundCard(Long recordId) {
        return recordRepository.findById(recordId)
                .switchIfEmpty(Mono.error(new RuntimeException("会员卡记录不存在")))
                .flatMap(record -> memberCardRepository.findByMemberCardIdAndDeletedAtIsNull(record.getMemberCardId())
                        .switchIfEmpty(Mono.error(new RuntimeException("会员卡类型不存在")))
                        .flatMap(card -> {
                            MemberCardType cardType = MemberCardType.valueOf(card.getMemberCardType());
                            return distributedLockService.executeWithLock(
                                record.getMemberId().toString(),
                                cardType.name(),
                                refundSagaHandler.executeRefund(recordId)
                            );
                        }));
    }

    @Override
    public Mono<Integer> processExpiredCards() {
        return recordRepository.findExpiredCards()
            .flatMap(record -> stateMachine.transition(record.getStatus(), CardEvent.EXPIRE)
                .flatMap(newState -> recordRepository.updateStatus(
                    record.getMemberCardRecordId(), newState.name())))
            .reduce(0, (count, result) -> count + 1)
            .doOnSuccess(count -> log.info("处理过期会员卡完成，共处理{}条", count));
    }

    private Mono<Void> createTransaction(MemberCardRecord record, TransactionType action, String remark) {
        MemberCardTransaction transaction = MemberCardTransaction.builder()
                .memberId(record.getMemberId())
                .memberCardId(record.getMemberCardId())
                .operationType(action.name())
                .changeAmount(record.getRemainingTimes())
                .changeBalance(record.getRemainingAmount())
                .afterRemainingCount(record.getRemainingTimes())
                .afterRemainingBalance(record.getRemainingAmount())
                .remark(remark)
                .build();

        return transactionService.createTransaction(transaction);
    }

    private void clearCardCache(Long memberCardId) {
        String cacheKey = MEMBER_CARD_CACHE_PREFIX + memberCardId;
        redisUtil.delete(cacheKey);
        log.debug("清除会员卡缓存, memberCardId: {}", memberCardId);
    }
}
