package cn.lihongjie.coal.user.service;

import cn.lihongjie.coal.base.dto.CommonQuery;
import cn.lihongjie.coal.base.dto.IdRequest;
import cn.lihongjie.coal.base.entity.BaseEntity;
import cn.lihongjie.coal.base.service.BaseService;
import cn.lihongjie.coal.common.Constants;
import cn.lihongjie.coal.common.Ctx;
import cn.lihongjie.coal.exception.BizException;
import cn.lihongjie.coal.organization.entity.OrganizationEntity;
import cn.lihongjie.coal.organization.service.OrganizationService;
import cn.lihongjie.coal.passwordDict.service.PasswordDictService;
import cn.lihongjie.coal.permission.dto.PermissionSimpleDto;
import cn.lihongjie.coal.permission.entity.PermissionEntity;
import cn.lihongjie.coal.permission.mapper.PermissionMapper;
import cn.lihongjie.coal.permission.service.PermissionService;
import cn.lihongjie.coal.resource.dto.ResourceDto;
import cn.lihongjie.coal.resource.mapper.ResourceMapper;
import cn.lihongjie.coal.resource.service.ResourceService;
import cn.lihongjie.coal.role.entity.RoleEntity;
import cn.lihongjie.coal.role.service.RoleService;
import cn.lihongjie.coal.sysconfig.service.SysConfigService;
import cn.lihongjie.coal.user.dto.*;
import cn.lihongjie.coal.user.entity.UserEntity;
import cn.lihongjie.coal.user.mapper.UserMapper;
import cn.lihongjie.coal.user.repository.UserRepository;

import com.nulabinc.zxcvbn.Strength;
import com.nulabinc.zxcvbn.Zxcvbn;

import io.vavr.collection.Stream;

import jakarta.annotation.PostConstruct;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root;

import lombok.extern.slf4j.Slf4j;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StopWatch;

import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

@Service
@Slf4j
@Transactional
public class UserService extends BaseService<UserEntity, UserRepository> {

    @Autowired UserRepository repository;

    @Autowired UserMapper mapper;
    @Autowired ConversionService conversionService;

    @Autowired RoleService roleService;

    @Autowired SysConfigService sysConfigService;

    @Autowired PasswordDictService passwordDictService;
    private Pbkdf2PasswordEncoder passwordEncoder;

    @PostConstruct
    public void init() {

        passwordEncoder = new Pbkdf2PasswordEncoder("", 8, 10000, 256);
    }

    @Autowired OrganizationService organizationService;

    public UserDto create(CreateUserDto request) {

        String organizationId = Ctx.currentUser().getOrganizationId();

        OrganizationEntity organization = organizationService.get(organizationId);

        if (organization.getMaxUser() != null
                && organization.getMaxUser() > 0
                && organization.getMaxUser() <= repository.countByOrganizationId(organizationId)) {
            throw new BizException("用户数量已经达到上限 " + organization.getMaxUser() + " 个");
        }

        StopWatch stopWatch = new StopWatch();

        checkDuplicateUserName(request.getUsername());
        try {
            stopWatch.start("encode");
            String password = request.getPassword();

            checkPassword(password);

            request.setPassword(passwordEncoder.encode(password));
            stopWatch.stop();
            UserEntity entity = mapper.toEntity(request);

            stopWatch.start("save");
            this.repository.save(entity);
            stopWatch.stop();

            stopWatch.start("getById");
            return getById(entity.getId());
        } finally {
            stopWatch.stop();
            log.info(stopWatch.prettyPrint());
        }
    }

    private void checkPassword(String password) {
        if (sysConfigService.isEnable(Constants.SYSCONFIG_PASSWORD_DICT_DETECT)) {
            if (passwordDictService.isInDict(password)) {
                throw new BizException("当前密码为常见密码，请重新设置");
            }
        }

        Integer strength = passwordStrength(password);
        int strength_min =
                Integer.parseInt(
                        sysConfigService.getConfigVal(Constants.SYSCONFIG_PASSWORD_STRENGTH_MIN));
        log.info("密码强度校验，当前密码强度为 {}，最低要求为 {}", strength, strength_min);
        if (strength_min > strength) {
            throw new BizException("密码强度过低，请重新设置");
        }
    }

    public Integer passwordStrength(String password) {

        Zxcvbn zxcvbn = new Zxcvbn();
        Strength strength = zxcvbn.measure(password);
        return strength.getScore();
    }

    private void checkDuplicateUserName(String username) {

        if (this.repository.countByUsername(username) > 0) {
            throw new BizException("用户名以及存在");
        }
    }

    public UserDto createOrgAdmin(CreateOrgAdminDto request) {

        StopWatch stopWatch = new StopWatch();
        checkDuplicateUserName(request.getUsername());
        checkPassword(request.getPassword());
        try {
            stopWatch.start("encode");
            request.setPassword(passwordEncoder.encode(request.getPassword()));
            stopWatch.stop();
            UserEntity entity = mapper.toEntity(request);
            entity.setOrgAdmin(true);
            entity.setSysAdmin(false);

            stopWatch.start("save");
            this.repository.save(entity);
            stopWatch.stop();

            stopWatch.start("getById");
            return getById(entity.getId());
        } finally {
            stopWatch.stop();
            log.info(stopWatch.prettyPrint());
        }
    }

    @CacheEvict(cacheNames = Constants.CACHE_USER_RESOURCES, key = "#request.id")
    public UserDto update(UpdateUserDto request) {
        UserEntity user = this.repository.get(request.getId());
        this.mapper.updateEntity(user, request);
        this.repository.save(user);
        return getById(user.getId());
    }

    public void delete(IdRequest request) {

        this.repository.deleteAllById(request.getIds());
    }

    public UserDto getById(String id) {

        UserEntity user = repository.get(id);

        List<RoleEntity> defaultRoles = roleService.getSysDefaultRoles();
        user.setOtherRoles(defaultRoles);
        if (BooleanUtils.isTrue(user.getOrgAdmin())) {
            List<RoleEntity> orgAdminRoles = roleService.getOrgAdminRoles();

            user.getOtherRoles().addAll(orgAdminRoles);
        }

        UserDto dto = mapper.toDto(user);
        if (Ctx.isLoggedIn()) {
            dto.setSessionId(Ctx.getSessionId());
        }

        return dto;
    }

    public UserDto changePwd(ChangeUserPwdDto request) {

        UserEntity user =
                repository.findById(request.getId()).orElseThrow(() -> new BizException("用户不存在"));

        if (!StringUtils.equals(request.getNewPassword(), request.getNewPassword2())) {
            throw new BizException("两次输入的密码不一致");
        }

        checkPassword(request.getNewPassword());

        user.setPassword(passwordEncoder.encode(request.getNewPassword()));

        repository.save(user);

        return getById(request.getId());
    }

    public void resetPwd(String userId, String password) {

        UserEntity user = repository.findById(userId).orElseThrow(() -> new BizException("用户不存在"));
        checkPassword(password);
        user.setPassword(passwordEncoder.encode(password));

        repository.save(user);
    }

    public Page<UserDto> list(CommonQuery query) {

        Page<UserEntity> page =
                repository.findAll(
                        query.specification(conversionService),
                        PageRequest.of(
                                query.getPageNo(),
                                query.getPageSize(),
                                Sort.by(query.getOrders())));

        return page.map(this.mapper::toDto);
    }

    public Optional<UserEntity> findByUsername(String username) {

        return this.findAll(
                        new Specification<UserEntity>() {
                            @Override
                            public Predicate toPredicate(
                                    Root<UserEntity> root,
                                    CriteriaQuery<?> query,
                                    CriteriaBuilder criteriaBuilder) {
                                return criteriaBuilder.equal(root.get("username"), username);
                            }
                        })
                .stream()
                .findFirst();
    }

    public boolean isValidPassword(String rawPassword, String encodedPassword) {
        return passwordEncoder.matches(rawPassword, encodedPassword);
    }

    public UserEntity initOrGetAdminUser(OrganizationEntity e) {

        UserEntity adminuser = this.repository.findByUsername("adminuser");

        if (adminuser == null) {

            adminuser = new UserEntity();

            adminuser.setUsername("adminuser");

            adminuser.setPassword(passwordEncoder.encode("adminuser@123"));

            adminuser.setOrgAdmin(true);
            adminuser.setName("超级管理员");

            adminuser.setSysAdmin(true);

            adminuser.setOrganizationId(e.getId());

            this.repository.save(adminuser);
        }

        return adminuser;
    }

    @Autowired ResourceMapper resourceMapper;

    @Autowired ResourceService resourceService;

    @Autowired PermissionService permissionService;

    @Autowired CacheManager cacheManager;

    @Cacheable(cacheNames = Constants.CACHE_USER_RESOURCES, key = "#id")
    public List<ResourceDto> resources(String id) {

        UserEntity user = get(id);

        if (user.getSysAdmin() != null && user.getSysAdmin()) {
            return resourceService.findAll().stream()
                    .map(x -> resourceMapper.toDto(x))
                    .collect(Collectors.toList());
        }

        return Stream.ofAll(user.allRoles())
                .flatMap(x -> x.getPermissions() == null ? Stream.empty() : x.getPermissions())
                .flatMap(x -> x.getResources() == null ? Stream.empty() : x.getResources())
                .appendAll(
                        Stream.ofAll(
                                        BooleanUtils.isTrue(user.getOrgAdmin())
                                                ? permissionService.getByTypes(
                                                        new String[] {"0", "1", "2"})
                                                : permissionService.getByTypes(
                                                        new String[] {"0", "1"}))
                                .flatMap(PermissionEntity::getResources))
                .distinctBy(BaseEntity::getId)
                .map(x -> resourceMapper.toDto(x))
                .collect(Collectors.toList());
    }

    @Autowired PermissionMapper permissionMapper;

    @Cacheable(cacheNames = Constants.CACHE_USER_PERMISSIONS, key = "#id")
    public List<PermissionSimpleDto> permissions(String id) {

        UserEntity user = get(id);

        if (user.getSysAdmin() != null && user.getSysAdmin()) {
            return permissionService.findAll().stream()
                    .map(x -> permissionMapper.toSimpleDto(x))
                    .collect(Collectors.toList());
        }

        return Stream.ofAll(user.allRoles())
                .flatMap(x -> x.getPermissions() == null ? Stream.empty() : x.getPermissions())
                .appendAll(
                        Stream.ofAll(
                                BooleanUtils.isTrue(user.getOrgAdmin())
                                        ? permissionService.getByTypes(new String[] {"0", "1", "2"})
                                        : permissionService.getByTypes(new String[] {"0", "1"})))
                .distinctBy(BaseEntity::getId)
                .map(x -> permissionMapper.toSimpleDto(x))
                .collect(Collectors.toList());
    }

    public void clearUserCache(String id) {
        if (StringUtils.isEmpty(id)) return;
        try {

            cacheManager.getCache(Constants.CACHE_USER_RESOURCES).evict(id);
        } catch (Exception e) {
            log.error("清除用户资源缓存失败", e);
        }
        try {

            cacheManager.getCache(Constants.CACHE_USER_PERMISSIONS).evict(id);
        } catch (Exception e) {
            log.error("清除用户权限缓存失败", e);
        }
    }

    public UserEntity findUniqUserByPhone(final String phone) {
        List<UserEntity> users = findAll(new Specification<UserEntity>() {
            @Override
            public Predicate toPredicate(Root<UserEntity> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
                return criteriaBuilder.equal(root.get("phone"), phone);
            }
        });
        if (CollectionUtils.isEmpty(users)){
            throw new BizException("手机号不存在");
        }


        if (users.size() > 1){
            throw new BizException("手机号重复");
        }

        UserEntity user = users.get(0);
        return user;
    }

    public boolean hasResource(String userId, String resourceId) {

        return this.repository.hasResource(userId, resourceId);
    }
}
