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

import cn.novalon.gym.manage.common.util.HtmlEscapeUtil;
import cn.novalon.gym.manage.member.config.WechatProperties;
import cn.novalon.gym.manage.member.entity.Member;
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.WechatOfficialService;
import cn.novalon.gym.manage.member.util.EsSyncUtils;
import cn.novalon.gym.manage.common.util.RedisUtil;
import cn.novalon.gym.manage.member.vo.WechatUserInfoVO;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;

/**
 * 微信服务号服务实现类
 * 
 * @author 付嘉
 * @date 2026-05-01
 */

@Slf4j
@Service
@RequiredArgsConstructor
public class WechatOfficialServiceImpl implements WechatOfficialService {

    private final IMemberRepository memberRepository;
    private final WechatProperties wechatProperties;
    private final WebClient webClient;
    private final MemberESRepository memberESRepository;
    private final ObjectMapper objectMapper = new ObjectMapper()
            .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    private final EsSyncUtils esSyncUtils;
    private final RedisUtil redisUtil;

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

    private static final String ACCESS_TOKEN_CACHE_PREFIX = "wechat:access_token:";
    private static final String MEMBER_INFO_CACHE_PREFIX = "member:info:";
    private static final long ACCESS_TOKEN_EXPIRE_SECONDS = 7000;
    private static final long CACHE_EXPIRE_SECONDS = 300;

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

    /**
     * 处理关注事件
     */
    @Override
    public Mono<Void> handleSubscribeEvent(String openId) {
        log.info("处理关注事件, openId: {}", openId);

        return getUserInfo(openId)
                .flatMap(userInfo -> {
                    String unionId = userInfo.getUnionid();
                    log.info("获取到用户信息 unionId: {}, nickname:  {}", unionId, userInfo.getNickname());

                    if (unionId != null && !unionId.isEmpty()) {
                        return memberRepository.findByUnionId(unionId)
                                .flatMap(existingMember -> {
                                    log.info("通过UnionID找到已有会员, memberId: {}", existingMember.getId());
                                    
                                    if (existingMember.getOfficialOpenId() == null || existingMember.getOfficialOpenId().isEmpty()) {
                                        log.info("用户先使用小程序，更新服务号OpenID: {}", openId);
                                        existingMember.setSubscribed(true);
                                        existingMember.setLastLoginAt(LocalDateTime.now());
                                        existingMember.setOfficialOpenId(openId);
                                        
                                        if (existingMember.getNickname() == null || existingMember.getNickname().isEmpty()) {
                                            existingMember.setNickname(HtmlEscapeUtil.escape(userInfo.getNickname()));
                                        }
                                        
                                        if (existingMember.getAvatar() == null || existingMember.getAvatar().isEmpty()) {
                                            existingMember.setAvatar(HtmlEscapeUtil.escape(userInfo.getHeadimgurl()));
                                        }
                                        
                                        return memberRepository.save(existingMember)
                                                .flatMap(saved -> {
                                                    memberSyncer.sync(saved);
                                                    return clearMemberCache(saved.getId())
                                                            .then(sendWelcomeMessage(openId));
                                                });
                                    } else {
                                        log.info("老用户关注服务号: memberId={}", existingMember.getId());
                                        existingMember.setSubscribed(true);
                                        existingMember.setLastLoginAt(LocalDateTime.now());
                                        
                                        return memberRepository.save(existingMember)
                                                .flatMap(saved -> {
                                                    memberSyncer.sync(saved);
                                                    return clearMemberCache(saved.getId())
                                                            .then(sendWelcomeMessage(openId));
                                                });
                                    }
                                })
                                .switchIfEmpty(Mono.defer(() -> {
                                    log.info("UnionID未找到，降级到服务号OpenID查询: {}", openId);
                                    return memberRepository.findByOfficialOpenId(openId)
                                            .flatMap(existingMember -> {
                                                log.info("通过服务号OpenID找到已有会员，更新UnionID, memberId: {}", existingMember.getId());
                                                existingMember.setUnionId(unionId);
                                                existingMember.setSubscribed(true);
                                                existingMember.setLastLoginAt(LocalDateTime.now());
                                                
                                                return memberRepository.save(existingMember)
                                                        .doOnSuccess(saved -> {
                                                            memberSyncer.sync(saved);
                                                            clearMemberCache(saved.getId());
                                                        })
                                                        .then(sendWelcomeMessage(openId));
                                            })
                                            .switchIfEmpty(Mono.defer(() -> {
                                                log.info("OpenID也未找到，创建新用户");
                                                return createNewMemberFromOfficial(unionId, openId)
                                                        .then(sendWelcomeMessage(openId));
                                            }));
                                }));
                    } else {
                        log.warn("用户没有UnionID，尝试通过服务号OpenID查询");
                        return memberRepository.findByOfficialOpenId(openId)
                                .flatMap(existingMember -> {
                                    log.info("通过服务号OpenID找到已有会员, memberId: {}", existingMember.getId());
                                    existingMember.setSubscribed(true);
                                    existingMember.setLastLoginAt(LocalDateTime.now());
                                    
                                    return memberRepository.save(existingMember)
                                            .doOnSuccess(saved -> {
                                                memberSyncer.sync(saved);
                                                clearMemberCache(saved.getId());
                                            })
                                            .then(sendWelcomeMessage(openId));
                                })
                                .switchIfEmpty(Mono.defer(() -> {
                                    log.info("OpenID也未找到，创建新会员（无UnionID）");
                                    return createNewMemberFromOfficial(unionId, openId)
                                            .then(sendWelcomeMessage(openId));
                                }));
                    }
                })
                .then();
    }

    /**
     * 处理取消关注事件
     */
    @Override
    public Mono<Void> handleUnsubscribeEvent(String openId) {
        log.info("处理取消关注事件, openId: {}", openId);

        return memberRepository.findByOfficialOpenId(openId)
                .flatMap(member -> {
                    log.info("找到会员，更新为未关注状态, memberId: {}", member.getId());
                    member.setSubscribed(false);
                    member.setLastLoginAt(LocalDateTime.now());
                    return memberRepository.save(member)
                            .flatMap(saved -> {
                                memberSyncer.sync(saved);
                                return clearMemberCache(saved.getId()).then();
                            });
                })
                .then()
                .switchIfEmpty(Mono.defer(() -> {
                    log.warn("未找到对应的会员记录, officialOpenId: {}", openId);
                    return Mono.empty();
                }))
                .onErrorResume(e -> {
                    log.error("处理取消关注事件失败", e);
                    return Mono.empty();
                });
    }

    /**
     * 获取微信用户信息
     */
    @Override
    public Mono<WechatUserInfoVO> getUserInfo(String openId) {
        log.debug("获取微信用户信息, openId: {}", openId);

        // 获取AccessToken
        return getAccessToken()
                .flatMap(accessToken -> {
                    String url = "https://api.weixin.qq.com/cgi-bin/user/info"
                            + "?access_token=" + accessToken
                            + "&openid=" + openId
                            + "&lang=zh_CN";

                    return webClient.get()
                            .uri(url)
                            .accept(MediaType.APPLICATION_JSON)
                            .retrieve()
                            .bodyToMono(String.class)
                            .map(responseJson -> {
                                log.info("微信API原始响应: {}", responseJson);
                                try {
                                    WechatUserInfoVO userInfo = objectMapper.readValue(responseJson, WechatUserInfoVO.class);
                                    log.info("解析后的用户信息 - unionId: {}, nickname: {}, openid: {}", 
                                            userInfo.getUnionid(), userInfo.getNickname(), userInfo.getOpenid());
                                    return userInfo;
                                } catch (Exception e) {
                                    log.error("解析微信用户信息失败", e);
                                    throw new RuntimeException("解析微信用户信息失败", e);
                                }
                            });
                });
    }

    /**
     * 通过 UnionID 关联小程序用户和服务号用户
     */
    @Override
    public Mono<Boolean> linkByUnionId(String unionId, String officialOpenId) {
        log.debug("关联小程序用户和服务号用户 unionId: {}, officialOpenId: {}", unionId, officialOpenId);

        return memberRepository.findByUnionId(unionId)
                .flatMap(member -> {
                    member.setSubscribed(true);
                    member.setLastLoginAt(LocalDateTime.now());
                    
                    if (officialOpenId != null && !officialOpenId.isEmpty()) {
                        member.setOfficialOpenId(officialOpenId);
                    }
                    return memberRepository.save(member)
                            .flatMap(saved -> {
                                memberSyncer.sync(saved);
                                return clearMemberCache(saved.getId())
                                        .then(Mono.just(saved));
                            })
                            .map(savedMember -> {
                                log.info("关联成功, memberId: {}", savedMember.getId());
                                return true;
                            });
                })
                .switchIfEmpty(Mono.defer(() -> {
                    log.warn("未找到对应的会员记录, unionId: {}", unionId);
                    return Mono.just(false);
                }))
                .onErrorResume(e -> {
                    log.error("关联失败", e);
                    return Mono.just(false);
                });
    }

    /**
     * 查询用户关注状态
     */
    @Override
    public Mono<Boolean> checkSubscribeStatus(Long memberId) {
        log.info("查询用户关注状态, memberId: {}", memberId);

        return memberRepository.findById(memberId)
                .map(member -> {
                    Boolean subscribed = member.getSubscribed();
                    boolean result = subscribed != null && subscribed;
                    log.info("查询用户关注状态结果, memberId: {}, subscribed: {}", memberId, result);
                    return result;
                })
                .defaultIfEmpty(false);
    }

    /**
     * 从服务号用户信息创建新会员
     */
    private Mono<Void> createNewMemberFromOfficial(String unionId, String openId) {
        Member member = Member.builder()
                .unionId(unionId)
                .officialOpenId(openId)
                .subscribed(true)
                .lastLoginAt(LocalDateTime.now())
                .build();
        
        log.info("新用户关注服务号，仅保存标识信息（UnionID和OpenID）");
        return memberRepository.save(member)
                .doOnSuccess(memberSyncer::sync)
                .doOnSuccess(savedMember -> {
                    log.info("从服务号创建新会员成功, memberId: {}", savedMember.getId());
                })
                .then();
    }

    /**
     * 获取微信AccessToken
     */
    private Mono<String> getAccessToken() {
        String cacheKey = ACCESS_TOKEN_CACHE_PREFIX + "mp";

        return redisUtil.get(cacheKey, String.class)
                .flatMap(cachedToken -> {
                    if (cachedToken != null) {
                        log.debug("从缓存获取服务号access_token");
                        return Mono.just(cachedToken);
                    }

                    String appId = wechatProperties.getMp().getAppId();
                    String appSecret = wechatProperties.getMp().getAppSecret();

                    String url = "https://api.weixin.qq.com/cgi-bin/token"
                            + "?grant_type=client_credential"
                            + "&appid=" + appId
                            + "&secret=" + appSecret;

                    return webClient.get()
                            .uri(url)
                            .accept(MediaType.APPLICATION_JSON)
                            .retrieve()
                            .bodyToMono(Map.class)
                            .flatMap(response -> {
                                if (response.containsKey("errcode")) {
                                    throw new RuntimeException("获取AccessToken失败: " + response.get("errmsg"));
                                }
                                String accessToken = (String) response.get("access_token");
                                return redisUtil.setWithExpire(cacheKey, accessToken, ACCESS_TOKEN_EXPIRE_SECONDS)
                                        .then(Mono.just(accessToken));
                            });
                });
    }

    /**
     * 发送欢迎消息给用户
     */
    private Mono<Void> sendWelcomeMessage(String openId) {
        log.info("发送欢迎消息给 openId: {}", openId);

        // 真正调用微信 API 发送消息（测试模式和生产模式都执行）
        return getAccessToken()
                .flatMap(accessToken -> {
                    String url = "https://api.weixin.qq.com/cgi-bin/message/custom/send"
                            + "?access_token=" + accessToken;

                    // 构建客服消息�?
                    Map<String, Object> messageBody = new HashMap<>();
                    messageBody.put("touser", openId);
                    messageBody.put("msgtype", "text");

                    Map<String, String> text = new HashMap<>();
                    text.put("content", "欢迎使用");
                    messageBody.put("text", text);

                    return webClient.post()
                            .uri(url)
                            .contentType(MediaType.APPLICATION_JSON)
                            .bodyValue(messageBody)
                            .retrieve()
                            .bodyToMono(Map.class)
                            .doOnSuccess(response -> {
                                if (response.containsKey("errcode") && !"0".equals(String.valueOf(response.get("errcode")))) {
                                    log.error("发送欢迎消息失败: {}", response.get("errmsg"));
                                } else {
                                    log.info("欢迎消息发送成功, openId: {}", openId);
                                }
                            })
                            .doOnError(error -> log.error("发送欢迎消息异常", error))
                            .then();
                })
                .onErrorResume(e -> {
                    log.error("发送欢迎消息失败, openId: {}", openId, e);
                    return Mono.empty(); // 即使发送失败也不影响主流程
                });
    }

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