package com.sikey.wa04.business.application.service.user;

import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.digest.MD5;
import com.sikey.wa04.business.application.converter.ChildConverter;
import com.sikey.wa04.business.application.service.BaseService;
import com.sikey.wa04.business.domain.adapter.ChildQueryAdapter;
import com.sikey.wa04.business.domain.adapter.DeviceQueryAdapter;
import com.sikey.wa04.business.domain.entity.*;
import com.sikey.wa04.business.domain.usecase.PhoneNumberFactory;
import com.sikey.wa04.business.infrastructure.dto.model.UserModel;
import com.sikey.wa04.business.infrastructure.dto.request.user.SignInRequest;
import com.sikey.wa04.business.infrastructure.dto.request.user.SignUpRequest;
import com.sikey.wa04.business.infrastructure.dto.response.user.SignInResponse;
import com.sikey.wa04.business.infrastructure.repository.UserRepository;
import com.sikey.wa04.common.exception.BusinessException;
import com.sikey.wa04.common.exception.errors.Errors;
import com.sikey.wa04.common.utils.AccessTokenUtil;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpStatus;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.util.List;
import java.util.Objects;

/**
 * 认证
 *
 * @author luoyangwei
 */
@Slf4j
@Service
public class AuthenticateService extends BaseService {

    @Resource
    private UserRepository userRepository;

    @Resource
    private ChildQueryAdapter childQueryAdapter;

    @Resource
    private DeviceQueryAdapter deviceQueryAdapter;

    private static final String SMS_CODE_PREFIX = "sms.code.%s";

    private static final String LOGIN_TOKEN = "login:token:%s";

    private static final String LOGIN_USER_ID = "login:user:id:%s";

    private static final Long LOGIN_TOKEN_EXPIRE = 7L;

    @Resource
    private RedisOperations<String, String> operations;

    public SignInResponse signIn(SignInRequest request) {
        PhoneNumber phoneNumber = PhoneNumberFactory.create(request.phoneNumber(), request.areaCode());
        log.info("User sign-in: {}", phoneNumber);

        // User input password
        String inputPassword = SecureUtil.md5(request.password());

        // Two login methods are supported. Email login only needs to verify the user's email address and password,
        // but the prerequisite is that the email address must be entered.
        final UserModel userModel;
        if (request.isEmailMode()) {
            userModel = userRepository.findByEmailAndPassword(request.email(), inputPassword);
            if (Objects.isNull(userModel)) {
                throw Errors.ACCOUNT_OR_PASSWORD_IS_INCORRECT.getException();
            }
        } else {
            userModel = userRepository.findByPhoneNumberAndAreaCode(request.phoneNumber(), request.areaCode());
            if (Objects.isNull(userModel)) {
                throw Errors.MOBILE_PHONE_NUMBER_NOT_REGISTERED.getException();
            } else if (Objects.nonNull(userModel.getIsRegistered()) && !userModel.getIsRegistered()) {
                // 兼容@see com.sikey.wa04.business.application.service.user.ContactsService#preRegistrationUser()
                // userRepository.updateIsRegisteredById(true, userModel.getId());
                throw Errors.MOBILE_PHONE_NUMBER_NOT_REGISTERED.getException();
            }
            if (StrUtil.isBlank(userModel.getPassword()) || !userModel.getPassword().equals(inputPassword)) {
                throw Errors.ACCOUNT_OR_PASSWORD_IS_INCORRECT.getException();
            }
        }


        // Generate token
        String accessToken = AccessTokenUtil.create(
                AccessTokenUtil.newBuilder().
                        setId(userModel.getId()).
                        setExpire(System.currentTimeMillis() + 1000 * 60 * 60 * 24 * 7));

        // Query all children of user
        List<Child> children = childQueryAdapter.findChildren(new UserId(userModel.getId()));
        for (Child child : children) {
            Device device = deviceQueryAdapter.findByChildId(new ChildId(child.getId()));
            if (Objects.nonNull(device)) {
                child.setDevice(device);
            }
        }

        // 删除验证码
        String key = String.format(SMS_CODE_PREFIX, request.areaCode() + request.phoneNumber());
        operations.delete(key);

        // 删除旧的token
        String existingToken = operations.opsForValue().
                get(String.format(LOGIN_USER_ID, userModel.getId()));
        if (StrUtil.isNotBlank(existingToken)) {
            operations.delete(String.format(LOGIN_TOKEN, existingToken));
            operations.delete(String.format(LOGIN_USER_ID, userModel.getId()));
        }

        // 记录token
        operations.opsForValue().
                set(String.format(LOGIN_TOKEN, accessToken), userModel.getId(), Duration.ofDays(LOGIN_TOKEN_EXPIRE));
        operations.opsForValue().
                set(String.format(LOGIN_USER_ID, userModel.getId()), accessToken, Duration.ofDays(LOGIN_TOKEN_EXPIRE));

        return new SignInResponse()
                .setUid(userModel.getId())
                .setToken(accessToken)
                .setPhoneNumber(userModel.getPhoneNumber())
                .setAreaCode(userModel.getAreaCode())
                .setChildList(children.stream().map(ChildConverter::to).toList());
    }

    public void signUp(SignUpRequest request) {
        PhoneNumber phoneNumber = new PhoneNumber(request.phoneNumber(), request.areaCode());
        String password = MD5.create().digestHex(request.password());

        UserModel user;
        if (request.isEmailMode()) {
            // 校验邮箱
            user = userRepository.findByEmail(request.email());
            if (Objects.nonNull(user) && Objects.nonNull(user.getIsRegistered()) && user.getIsRegistered()) {
                throw new BusinessException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "邮箱已注册");
            }

            user = userRepository.findByEmailAndPassword(request.email(), password);
        } else {
            // 校验手机号
            user = userRepository.findByPhoneNumberAndAreaCode(request.phoneNumber(), request.areaCode());
            if (Objects.nonNull(user) && Objects.nonNull(user.getIsRegistered()) && user.getIsRegistered()) {
                throw new BusinessException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "手机号已注册");
            }

            // 删除验证码
            String key = String.format(SMS_CODE_PREFIX, request.areaCode() + request.phoneNumber());
            operations.delete(key);
        }

        if (Objects.isNull(user)) {
            user = new UserModel().
                    setId(UUID.randomUUID().toString()).
                    setEmail(request.email()).
                    setPhoneNumber(request.phoneNumber()).
                    setAreaCode(request.areaCode()).
                    setPassword(password)
                    // 将数据改成已注册
                    .setIsRegistered(true);
        } else {
            user.setEmail(request.email())
                    .setPhoneNumber(request.phoneNumber())
                    .setAreaCode(request.areaCode())
                    .setPassword(password)
                    // 将数据改成已注册
                    .setIsRegistered(true);

        }

        log.info("User sign-up: {}", user);
        userRepository.save(user);
    }

}
