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

import cn.novalon.gym.manage.auth.config.SmsProperties;
import cn.novalon.gym.manage.auth.dto.PhoneCodeLoginDto;
import cn.novalon.gym.manage.auth.dto.PhoneLoginDto;
import cn.novalon.gym.manage.auth.service.PhoneAuthService;
import cn.novalon.gym.manage.auth.service.SmsService;
import cn.novalon.gym.manage.auth.vo.PhoneLoginVO;
import cn.novalon.gym.manage.common.exception.ErrorCode;
import cn.novalon.gym.manage.common.exception.SystemException;
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.util.AesUtil;
import cn.novalon.gym.manage.member.util.EsSyncUtils;
import cn.novalon.gym.manage.member.util.MemberNoGenerator;
import cn.novalon.gym.manage.sys.security.JwtTokenProvider;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.dypnsapi.model.v20170525.GetMobileRequest;
import com.aliyuncs.dypnsapi.model.v20170525.GetMobileResponse;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.exceptions.ServerException;
import com.aliyuncs.profile.DefaultProfile;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@Slf4j
@Service
@RequiredArgsConstructor
public class PhoneAuthServiceImpl implements PhoneAuthService {

    private final IMemberRepository memberRepository;
    private final MemberESRepository memberESRepository;
    private final EsSyncUtils esSyncUtils;
    private final JwtTokenProvider jwtTokenProvider;
    private final SmsService smsService;
    private final SmsProperties smsProperties;

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

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

    @Override
    public Mono<PhoneLoginVO> oneClickLogin(PhoneLoginDto request) {
        log.info("手机号一键登录请求, accessToken: {}, openid: {}", request.getAccessToken(), request.getOpenid());

        return Mono.fromCallable(() -> getPhoneByToken(request.getAccessToken(), request.getOpenid()))
                .flatMap(phone -> {
                    log.info("通过access_token获取手机号成功: {}", maskPhone(phone));
                    String encryptedPhone = encryptPhone(phone);

                    return memberRepository.findByPhone(encryptedPhone)
                            .flatMap(existingMember -> {
                                log.info("手机号已注册，直接登录, memberId: {}", existingMember.getId());
                                return doLogin(existingMember, false, request);
                            })
                            .switchIfEmpty(Mono.defer(() -> {
                                log.info("手机号未注册，创建新会员");
                                return createNewMemberAndLogin(request, encryptedPhone);
                            }));
                })
                .onErrorResume(e -> {
                    log.error("一键登录失败", e);
                    return Mono.error(new SystemException(ErrorCode.AUTH_PHONE_ERROR, "手机号获取失败: " + e.getMessage()));
                });
    }

    /**
     * 通过access_token和openid获取手机号
     * 使用阿里云DYPNS API
     */
    private String getPhoneByToken(String accessToken, String openid) throws Exception {
        DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou", smsProperties.getAccessKeyId(), smsProperties.getAccessKeySecret());
        IAcsClient client = new DefaultAcsClient(profile);
        GetMobileRequest request = new GetMobileRequest();
        request.setAccessToken(accessToken);

        try {
            GetMobileResponse response = client.getAcsResponse(request);
            if ("OK".equals(response.getCode())) {
                return response.getGetMobileResultDTO().getMobile();
            }
            log.warn("DYPNS API返回错误: code={}, message={}", response.getCode(), response.getMessage());
            throw new SystemException(ErrorCode.AUTH_PHONE_ERROR, "手机号获取失败: " + response.getMessage());
        } catch (ServerException e) {
            log.error("阿里云DYPNS API服务端异常", e);
            throw new SystemException(ErrorCode.AUTH_PHONE_ERROR, "手机号获取失败");
        } catch (ClientException e) {
            log.error("阿里云DYPNS API客户端异常", e);
            throw new SystemException(ErrorCode.AUTH_PHONE_ERROR, "手机号获取失败");
        }
    }

    /**
     * 手机号脱敏显示
     */
    private String maskPhone(String phone) {
        if (phone == null || phone.length() < 11) {
            return phone;
        }
        return phone.substring(0, 3) + "****" + phone.substring(7);
    }

    @Override
    public Mono<Boolean> sendSmsCode(String phone) {
        log.info("发送短信验证码, phone: {}", phone);
        return Mono.fromCallable(() -> smsService.sendVerificationCode(phone));
    }

    @Override
    public Mono<PhoneLoginVO> codeLogin(PhoneCodeLoginDto request) {
        log.info("手机号验证码登录, phone: {}", request.getPhone());

        return Mono.fromCallable(() -> smsService.verifyCode(request.getPhone(), request.getCode()))
                .flatMap(verified -> {
                    if (!verified) {
                        log.warn("验证码验证失败, phone: {}", request.getPhone());
                        return Mono.error(new SystemException(ErrorCode.SYSTEM_INTERNAL_ERROR, "验证码错误或已过期"));
                    }

                    String encryptedPhone = encryptPhone(request.getPhone());

                    return memberRepository.findByPhone(encryptedPhone)
                            .flatMap(existingMember -> {
                                log.info("手机号已注册，直接登录, memberId: {}", existingMember.getId());
                                return doLogin(existingMember, false, null);
                            })
                            .switchIfEmpty(Mono.defer(() -> {
                                log.info("手机号未注册，创建新会员");
                                PhoneLoginDto registerRequest = new PhoneLoginDto();
                                return createNewMemberAndLogin(registerRequest, encryptedPhone);
                            }));
                });
    }

    private Mono<PhoneLoginVO> createNewMemberAndLogin(PhoneLoginDto request, String encryptedPhone) {
        String memberNo = MemberNoGenerator.generate();
        log.info("生成会员号: {}", memberNo);

        Member member = new Member();
        member.setMemberNo(memberNo);
        member.setPhone(encryptedPhone);
        member.setNickname(request != null ? request.getNickname() : null);
        member.setAvatar(request != null ? request.getAvatar() : null);
        member.setLastLoginAt(LocalDateTime.now());
        member.setIsDeleted(false);

        return memberRepository.save(member)
                .doOnSuccess(memberSyncer::sync)
                .flatMap(savedMember -> {
                    log.info("新会员创建成功, memberId: {}, memberNo: {}", savedMember.getId(), savedMember.getMemberNo());
                    return doLogin(savedMember, true, request);
                });
    }

    private Mono<PhoneLoginVO> doLogin(Member member, boolean isNewUser, PhoneLoginDto request) {
        log.info("登录成功, memberId: {}", member.getId());

        member.setLastLoginAt(LocalDateTime.now());

        if (isNewUser && request != null) {
            if ((member.getNickname() == null || member.getNickname().isEmpty())
                    && request.getNickname() != null && !request.getNickname().isEmpty()) {
                member.setNickname(request.getNickname());
            }
            if ((member.getAvatar() == null || member.getAvatar().isEmpty())
                    && request.getAvatar() != null && !request.getAvatar().isEmpty()) {
                member.setAvatar(request.getAvatar());
            }
        }

        return memberRepository.save(member)
                .doOnSuccess(memberSyncer::sync)
                .map(this::buildLoginResponse);
    }

    private PhoneLoginVO buildLoginResponse(Member member) {
        boolean needCompleteInfo = member.getNickname() == null || member.getNickname().isEmpty();

        List<String> roles = new ArrayList<>();
        String accessToken = jwtTokenProvider.generateToken(String.valueOf(member.getId()), member.getId(), roles);

        log.info("JWT Token 生成成功, memberId: {}", member.getId());

        PhoneLoginVO vo = new PhoneLoginVO();
        vo.setMemberId(member.getId());
        vo.setMemberNo(member.getMemberNo());
        vo.setAccessToken(accessToken);
        vo.setRefreshToken(accessToken);
        vo.setExpiresIn(86400);
        vo.setIsNewUser(member.getCreatedAt() == null ? false :
                member.getCreatedAt().isAfter(LocalDateTime.now().minusMinutes(1)));
        vo.setNeedCompleteInfo(needCompleteInfo);
        vo.setNickname(member.getNickname());
        vo.setAvatar(member.getAvatar());
        vo.setPhone(member.getPhone() != null ? decryptPhone(member.getPhone()) : null);
        return vo;
    }

    private String encryptPhone(String phoneNumber) {
        try {
            return AesUtil.encrypt(phoneNumber);
        } catch (Exception e) {
            log.error("手机号加密失败", e);
            throw new SystemException(ErrorCode.SYSTEM_INTERNAL_ERROR, "手机号加密失败");
        }
    }

    private String decryptPhone(String encryptedPhone) {
        try {
            return AesUtil.decrypt(encryptedPhone);
        } catch (Exception e) {
            log.error("手机号解密失败", e);
            return null;
        }
    }
}
