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

import cn.novalon.gym.manage.common.exception.ConflictException;
import cn.novalon.gym.manage.common.exception.ErrorCode;
import cn.novalon.gym.manage.common.exception.NotFoundException;
import cn.novalon.gym.manage.common.exception.SystemException;
import cn.novalon.gym.manage.common.util.HtmlEscapeUtil;
import cn.novalon.gym.manage.member.dto.SearchMemberDto;
import cn.novalon.gym.manage.member.dto.UpdateMemberInfoDto;
import cn.novalon.gym.manage.member.entity.Member;
import cn.novalon.gym.manage.member.enums.GenderEnum;
import cn.novalon.gym.manage.member.enums.MemberCardType;
import cn.novalon.gym.manage.member.es.entity.MemberES;
import cn.novalon.gym.manage.member.es.repository.MemberESRepository;
import cn.novalon.gym.manage.member.repository.IMemberRepository;
import cn.novalon.gym.manage.member.service.MemberService;
import cn.novalon.gym.manage.member.util.AesUtil;
import cn.novalon.gym.manage.member.util.BeanConvertUtil;
import cn.novalon.gym.manage.member.util.EsSyncUtils;
import cn.novalon.gym.manage.common.util.RedisUtil;
import cn.novalon.gym.manage.member.vo.MemberCardInfoVO;
import cn.novalon.gym.manage.member.vo.MemberDetailVO;
import cn.novalon.gym.manage.member.vo.MemberInfoVO;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.PageRequest;
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;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 会员服务实现
 *
 * @author 付嘉
 * @date 2026-05-01
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class MemberServiceImpl implements MemberService {

    private final IMemberRepository memberRepository;
    private final MemberESRepository memberESRepository;
    private final EsSyncUtils esSyncUtils;
    private final RedisUtil redisUtil;

    private EsSyncUtils.EntitySyncer<Member, MemberES, String> memberSyncer;

    private static final String MEMBER_INFO_CACHE_PREFIX = "member:info:";
    private static final String MEMBER_DETAIL_CACHE_PREFIX = "member:detail:";
    private static final long CACHE_EXPIRE_SECONDS = 300;

    @PostConstruct
    public void init() {
        this.memberSyncer = esSyncUtils.bind(Member.class, MemberES.class, memberESRepository);
    }

    @Override
    public Mono<MemberInfoVO> getMemberInfo(Long memberId) {
        String cacheKey = MEMBER_INFO_CACHE_PREFIX + memberId;

        // 先查缓存
        return redisUtil.get(cacheKey, MemberInfoVO.class)
                .flatMap(cached -> {
                    if (cached != null) {
                        log.debug("从缓存获取会员信息, memberId: {}", memberId);
                        return Mono.just(cached);
                    }
                    // 缓存没有，查数据库
                    return queryFromDatabaseAndCache(memberId, cacheKey);
                })
                .switchIfEmpty(Mono.defer(() -> {
                    // 缓存返回null，查数据库
                    return queryFromDatabaseAndCache(memberId, cacheKey);
                }));
    }

    /**
     * 从数据库查询并更新缓存
     */
    private Mono<MemberInfoVO> queryFromDatabaseAndCache(Long memberId, String cacheKey) {
        return memberRepository.findById(memberId)
                .map(this::buildMemberInfoResponse)
                .flatMap(vo -> {
                    // 查询到数据后更新缓存
                    return redisUtil.setWithExpire(cacheKey, vo, CACHE_EXPIRE_SECONDS)
                            .then(Mono.just(vo));
                })
                .switchIfEmpty(Mono.error(() -> {
                    log.error("会员不存在: memberId={}", memberId);
                    throw new NotFoundException(ErrorCode.NOT_FOUND_USER, "会员不存在");
                }));
    }

    @Override
    public Mono<MemberInfoVO> updateMemberInfo(Long memberId, UpdateMemberInfoDto updateDto) {
        log.info("会员更新个人信息, memberId: {}", memberId);

        return memberRepository.findById(memberId)
                .flatMap(member -> {
                    if (updateDto.getNickname() != null) {
                        member.setNickname(HtmlEscapeUtil.escape(updateDto.getNickname()));
                    }
                    if (updateDto.getGender() != null) {
                        member.setGender(updateDto.getGender().getCode());
                    }
                    if (updateDto.getBirthday() != null) {
                        member.setBirthday(updateDto.getBirthday());
                    }
                    if (updateDto.getAvatar() != null) {
                        member.setAvatar(updateDto.getAvatar());
                    }
                    if (updateDto.getAddress() != null) {
                        member.setAddress(HtmlEscapeUtil.escape(updateDto.getAddress()));
                    }

                    return memberRepository.save(member);
                })
                .flatMap(savedMember -> {
                    memberSyncer.sync(savedMember);
                    return clearMemberCache(memberId)
                            .then(Mono.just(savedMember));
                })
                .map(savedMember -> {
                    log.info("会员信息更新成功, memberId: {}", savedMember.getId());
                    return buildMemberInfoResponse(savedMember);
                })
                .switchIfEmpty(Mono.error(() -> {
                    log.error("会员不存在: memberId={}", memberId);
                    throw new NotFoundException(ErrorCode.NOT_FOUND_USER, "会员不存在");
                }));
    }

    private MemberInfoVO buildMemberInfoResponse(Member member) {
        String phone = member.getPhone();
        String maskedPhone = phone != null ? phone.replace(phone.substring(3, 7), "****") : null;

        GenderEnum genderEnum = GenderEnum.fromCode(member.getGender());

        return MemberInfoVO.builder()
                .id(member.getId())
                .nickname(member.getNickname())
                .phone(maskedPhone)
                .gender(genderEnum)
                .genderDesc(genderEnum.getDesc())
                .birthday(member.getBirthday())
                .avatar(member.getAvatar())
                .hasPhone(phone != null)
                .isSubscribed(member.getSubscribed() != null && member.getSubscribed())
                .build();
    }

    @Override
    public Mono<Boolean> adminUpdatePhone(Long memberId, String phone) {
        log.info("管理端录入手机号, memberId: {}, phone: {}", memberId, phone);

        String encryptedPhone;
        try {
            encryptedPhone = AesUtil.encrypt(phone);
            log.info("手机号加密成功");
        } catch (Exception e) {
            log.error("手机号加密失败", e);
            throw new SystemException(ErrorCode.SYSTEM_INTERNAL_ERROR, "手机号加密失败: " + e.getMessage());
        }

        return memberRepository.findByPhone(encryptedPhone)
                .<Boolean>flatMap(existingMember -> {
                    if (existingMember.getId().equals(memberId)) {
                        log.warn("手机号已是当前用户的: memberId={}", memberId);
                        throw new ConflictException(ErrorCode.CONFLICT_DUPLICATE_USER, "重复绑定");
                    } else {
                        log.warn("手机号已被其他用户绑定: memberId={}, existingMemberId={}",
                                memberId, existingMember.getId());
                        throw new ConflictException(ErrorCode.CONFLICT_DUPLICATE_USER, "该手机号已被其他会员绑定");
                    }
                })
                .switchIfEmpty(Mono.defer(() -> {
                    log.info("手机号未被占用，可以绑定");
                    return updateMemberPhone(memberId, encryptedPhone);
                }));
    }

    @Override
    public Flux<MemberES> searchMember(SearchMemberDto searchMemberDto) {
        log.info("搜索会员, searchValue: {}, pageNum: {}, pageSize: {}",
                searchMemberDto.getSearchValue(),
                searchMemberDto.getPageNum(),
                searchMemberDto.getPageSize());

        String searchValue = searchMemberDto.getSearchValue();

        if (searchValue != null && searchValue.matches("^1[3-9]\\d{9}$")) {
            log.debug("搜索值为手机号格式，进行加密处理");
            searchValue = AesUtil.encrypt(searchValue);
        }

        Pageable pageable = PageRequest.of(
                searchMemberDto.getPageNum() - 1,
                searchMemberDto.getPageSize()
        );

        if (searchValue == null) {
            log.warn("搜索值为空，返回空结果");
            return Flux.empty();
        }

        return memberESRepository.findByMemberNoOrPhoneOrNicknameContaining(
                searchValue,
                searchValue,
                searchValue,
                pageable
        );
    }

    @Override
    public Flux<Member> findAll(Integer pageNum, Integer pageSize) {
        log.info("查询所有会员列表, pageNum: {}, pageSize: {}", pageNum, pageSize);

        Pageable pageable = PageRequest.of(
                pageNum - 1,
                pageSize
        );

        return memberRepository.findAllBy(pageable);
    }

    @Override
    public Mono<MemberDetailVO> getMemberDetail(Long memberId) {
        log.info("查询会员详情, memberId: {}", memberId);

        String cacheKey = MEMBER_DETAIL_CACHE_PREFIX + memberId;

        return redisUtil.get(cacheKey, MemberDetailVO.class)
                .flatMap(cached -> {
                    if (cached != null) {
                        log.debug("从缓存获取会员详情, memberId: {}", memberId);
                        return Mono.just(cached);
                    }
                    return memberRepository.findById(memberId)
                            .zipWith(
                                    memberRepository.findCardRecordsWithCardInfoByMemberId(memberId)
                                            .collectList(),
                                    (baseInfo, cardList) -> {
                                        MemberDetailVO memberDetailVO = BeanConvertUtil.toBean(baseInfo, MemberDetailVO.class);

                                        GenderEnum genderEnum = GenderEnum.fromCode(baseInfo.getGender());
                                        memberDetailVO.setGenderDesc(genderEnum.getDesc());

                                        List<MemberCardInfoVO> enrichedCards = cardList.stream()
                                                .peek(vo -> {
                                                    if (vo.getMemberCardType() != null) {
                                                        try {
                                                            MemberCardType cardType = MemberCardType.valueOf(vo.getMemberCardType());
                                                            vo.setMemberCardTypeDesc(cardType.getDesc());
                                                        } catch (IllegalArgumentException e) {
                                                            vo.setMemberCardTypeDesc(vo.getMemberCardType());
                                                        }
                                                    }
                                                    if (vo.getMemberCardStatus() != null) {
                                                        vo.setMemberCardStatusDesc(vo.getMemberCardStatus() == 1 ? "上架" : "下架");
                                                    }
                                                })
                                                .collect(Collectors.toList());
                                        memberDetailVO.setMemberCards(enrichedCards);

                                        long activeCount = enrichedCards.stream()
                                                .filter(card -> card.getMemberCardStatus() != null && card.getMemberCardStatus() == 1)
                                                .count();
                                        memberDetailVO.setActiveCardCount((int) activeCount);
                                        memberDetailVO.setInactiveCardCount(enrichedCards.size() - (int) activeCount);

                                        return memberDetailVO;
                                    }
                            )
                            .flatMap(vo -> redisUtil.setWithExpire(cacheKey, vo, CACHE_EXPIRE_SECONDS)
                                    .then(Mono.just(vo)));
                });
    }


    @Override
    public Mono<Boolean> adminUpdateMemberInfo(Long memberId, UpdateMemberInfoDto updateDto) {
        log.info("前台管理端编辑会员信息, memberId: {}", memberId);

        return memberRepository.findById(memberId)
                .switchIfEmpty(Mono.error(() -> {
                    log.error("会员不存在: memberId={}", memberId);
                    throw new NotFoundException(ErrorCode.NOT_FOUND_USER, "会员不存在");
                }))
                .flatMap(member -> {
                    if (updateDto.getNickname() != null) {
                        member.setNickname(HtmlEscapeUtil.escape(updateDto.getNickname()));
                    }
                    if (updateDto.getGender() != null) {
                        member.setGender(updateDto.getGender().getCode());
                    }
                    if (updateDto.getBirthday() != null) {
                        member.setBirthday(updateDto.getBirthday());
                    }
                    if (updateDto.getAvatar() != null) {
                        member.setAvatar(updateDto.getAvatar());
                    }
                    if (updateDto.getAddress() != null) {
                        member.setAddress(HtmlEscapeUtil.escape(updateDto.getAddress()));
                    }

                    return memberRepository.save(member);
                })
                .flatMap(savedMember -> {
                    memberSyncer.sync(savedMember);
                    return clearMemberCache(memberId)
                            .then(Mono.just(true));
                })
                .onErrorResume(e -> {
                    log.error("编辑会员信息失败, memberId: {}, error: {}", memberId, e.getMessage(), e);
                    return Mono.just(false);
                });
    }

    private Mono<Boolean> updateMemberPhone(Long memberId, String encryptedPhone) {
        return memberRepository.findById(memberId)
                .flatMap(member -> {
                    member.setPhone(encryptedPhone);
                    member.setLastLoginAt(LocalDateTime.now());

                    return memberRepository.save(member)
                            .flatMap(savedMember -> {
                                memberSyncer.sync(savedMember);
                                return clearMemberCache(memberId)
                                        .then(Mono.just(savedMember));
                            })
                            .map(savedMember -> {
                                log.info("手机号录入成功, memberId: {}", savedMember.getId());
                                return true;
                            });
                })
                .switchIfEmpty(Mono.error(() -> {
                    log.error("会员不存在: memberId={}", memberId);
                    throw new NotFoundException(ErrorCode.NOT_FOUND_USER, "会员不存在");
                }));
    }

    private Mono<Long> clearMemberCache(Long memberId) {
        String infoCacheKey = MEMBER_INFO_CACHE_PREFIX + memberId;
        String detailCacheKey = MEMBER_DETAIL_CACHE_PREFIX + memberId;
        return redisUtil.delete(infoCacheKey)
                .then(redisUtil.delete(detailCacheKey))
                .doOnSuccess(result -> log.debug("清除会员缓存, memberId: {}", memberId));
    }
}