package rd.service.user;

import cn.hutool.crypto.digest.BCrypt;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.yitter.idgen.YitIdHelper;
import lombok.extern.slf4j.Slf4j;
import rd.dao.mapper.user.UserBasicMapper;
import rd.data.code.ServiceCode;
import rd.data.dto.user.UserBasicInfoDTO;
import rd.data.db.table.UserBasic;

import rd.exception.ServiceException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public class UserBasicService {
    private static final Cache<String, UserBasic> usernameCache = Caffeine.newBuilder()
            .expireAfterWrite(2, TimeUnit.DAYS)
            .initialCapacity(256)
            .maximumSize(2048)
            .build();

    private static final Cache<Long, UserBasic> idCache = Caffeine.newBuilder()
            .expireAfterWrite(2, TimeUnit.DAYS)
            .initialCapacity(256)
            .maximumSize(2048)
            .build();

    private final UserBasicMapper userBasicMapper;

    public UserBasicService(UserBasicMapper userBasicMapper) {
        this.userBasicMapper = userBasicMapper;
    }

    public UserBasic getUserBasicByUsername(String username) {
        UserBasic userBasic = usernameCache.getIfPresent(username);
        if (userBasic != null) {
            return userBasic;
        }

        userBasic = userBasicMapper.selectOne(
                new QueryWrapper<UserBasic>()
                        .eq("username", username)
                        .eq("deleted", false)
                        .last("limit 1")
        );

        return putCache(userBasic);
    }

    public UserBasic getUserBasicById(Long id) {
        return getUserBasicById(id, true);
    }

    public UserBasic getUserBasicById(Long id, boolean useCache) {
        UserBasic userBasic = idCache.getIfPresent(id);
        if (useCache && userBasic != null) {
            return userBasic;
        }

        userBasic = userBasicMapper.selectOne(
                new QueryWrapper<UserBasic>()
                        .select("id", "username", "password", "type")
                        .eq("id", id)
                        .eq("deleted", false)
                        .last("limit 1")
        );

        return putCache(userBasic);
    }

    private UserBasic putCache(UserBasic userBasic) {
        if (userBasic == null) {
            return null;
        }

        usernameCache.put(userBasic.getUsername(), userBasic);
        idCache.put(userBasic.getId(), userBasic);

        return userBasic;
    }

    public UserBasic addUser(String username, String password, short type) {
        long id = YitIdHelper.nextId();
        return this.addUser(id, username, password, type);
    }

    public UserBasic addUser(Long id, String username, String password, short type) {
        var find = this.getUserBasicById(id);

        UserBasic userBasic = new UserBasic();
        if (find != null) {
            String encryptedPassword = BCrypt.hashpw(password);

            userBasic.setId(id);
            userBasic.setUsername(username);
            userBasic.setPassword(encryptedPassword);
            userBasic.setType(type);

            userBasicMapper.update(userBasic, new QueryWrapper<UserBasic>()
                    .eq("id", id)
                    .eq("deleted", false)
            );
        } else {
            // 默认密码同用户名
            if (password == null || password.isEmpty()) {
                password = username;
            }

            String encryptedPassword = BCrypt.hashpw(password);

            userBasic.setId(id);
            userBasic.setUsername(username);
            userBasic.setPassword(encryptedPassword);
            userBasic.setType(type);

            userBasicMapper.insert(userBasic);
        }

        putCache(userBasic);
        return userBasic;
    }

    @Transactional
    public UserBasic updatePassword(Long uid, String newPassword) {
        var userBasic = this.getUserBasicById(uid);
        if (userBasic == null) {
            throw ServiceException.error(ServiceCode.UserNotExists);
        }

        String encryptedPassword = BCrypt.hashpw(newPassword);

        userBasic.setPassword(encryptedPassword);
        userBasic.setEditTime(LocalDateTime.now());
        userBasicMapper.update(userBasic, new QueryWrapper<UserBasic>()
                .eq("id", uid)
                .eq("deleted", false)
        );

        return this.getUserBasicById(uid, false);
    }

    @Transactional
    public UserBasic updateUser(Long id, UserBasicInfoDTO dto) {
        var userBasic = this.getUserBasicById(id, false);
        if (userBasic == null) {
            throw ServiceException.error(ServiceCode.UserNotExists);
        }

        String encryptedPassword = BCrypt.hashpw(dto.password());

        userBasic.setId(id);
        userBasic.setUsername(dto.username());
        userBasic.setPassword(encryptedPassword);
        userBasic.setType(dto.type());
        userBasic.setEditTime(LocalDateTime.now());
        userBasicMapper.update(userBasic, new QueryWrapper<UserBasic>()
                .eq("id", id)
                .eq("deleted", false)
        );

        return this.getUserBasicById(id, false);
    }

    public void deleteUser(Long id) {
        UserBasic userBasic = this.getUserBasicById(id);
        if (userBasic == null) {
            throw ServiceException.error(ServiceCode.UserNotExists);
        }

        userBasic.setDeleted(true);
        userBasic.setEditTime(LocalDateTime.now());
        userBasicMapper.update(userBasic, new QueryWrapper<UserBasic>()
                .eq("id", id)
        );

        var cache = idCache.getIfPresent(id);
        if (cache != null) {
            usernameCache.invalidate(cache.getUsername());
        }
        idCache.invalidate(id);
    }
}
