package cn.skcks.matrix.v2.services.role;

import cn.hutool.core.util.IdUtil;
import cn.skcks.matrix.v2.model.role.dto.RoleRoutePermission;
import cn.skcks.matrix.v2.model.services.ServiceResult;
import cn.skcks.matrix.v2.orm.mybatis.dynamic.mapper.RoleDynamicSqlSupport;
import cn.skcks.matrix.v2.orm.mybatis.dynamic.mapper.RoleMapper;
import cn.skcks.matrix.v2.orm.mybatis.dynamic.mapper.UserDynamicSqlSupport;
import cn.skcks.matrix.v2.orm.mybatis.dynamic.mapper.UserMapper;
import cn.skcks.matrix.v2.orm.mybatis.dynamic.model.Role;
import cn.skcks.matrix.v2.orm.mybatis.dynamic.model.User;
import cn.skcks.matrix.v2.services.casbin.CasbinService;
import cn.skcks.matrix.v2.services.casbin.Permission;
import cn.skcks.matrix.v2.services.casbin.Permission.Identify;
import cn.skcks.matrix.v2.services.user.UserService;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.shardingsphere.transaction.annotation.ShardingSphereTransactionType;
import org.mybatis.dynamic.sql.SqlBuilder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.text.MessageFormat;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;

@Slf4j
@Service
@RequiredArgsConstructor
public class RoleServiceImpl implements RoleService {
    private final RoleMapper roleMapper;
    private final CasbinService casbinService;

    private final UserService userService;
    private final UserMapper userMapper;

    @Override
    public PageInfo<Role> getRoles(int page, int size, boolean all) {

        PageInfo<Role> pageInfo;
        if (all) {
            pageInfo = new PageInfo<>();
            List<Role> roles = roleMapper.select(u -> u.orderBy(RoleDynamicSqlSupport.id.descending()));
            pageInfo.setList(roles);
            pageInfo.setTotal(roles.size());
            pageInfo.setPageSize(roles.size());
            pageInfo.setPageNum(1);
            pageInfo.setPages(1);
            return pageInfo;
        }

        try (Page<Role> rolePage = PageHelper.startPage(page, size)) {
            pageInfo = rolePage.doSelectPageInfo(() -> roleMapper.select(u -> u.orderBy(RoleDynamicSqlSupport.id.descending())));
        }

        List<Role> roles = pageInfo.getList();
        log.info("共 {} 条, 每页 {} 条, 第 {}/{} 页, {} 条数据",
                pageInfo.getTotal(),
                pageInfo.getPageSize(),
                pageInfo.getPageNum(),
                pageInfo.getPages(),
                roles.size());

        return pageInfo;
    }

    @Override
    public void initRole(String role, String name, boolean active) {
        Optional<Role> roleModel = roleMapper.selectOne(s -> s
                .where(RoleDynamicSqlSupport.role.role, SqlBuilder.isEqualTo(role))
                .and(RoleDynamicSqlSupport.systemId, SqlBuilder.isEqualTo(CasbinService.DEFAULT_SYSTEM))
                .limit(1));

        if (roleModel.isEmpty()) {
            Role r = new Role();
            r.setId(IdUtil.getSnowflakeNextId());
            r.setActive(active);
            r.setRole(role);
            r.setName(name);
            r.setSystemId(CasbinService.DEFAULT_SYSTEM);
            roleMapper.insert(r);
        }
    }

    private boolean existRole(String role, String system) {
        Optional<Role> roleModel = roleMapper.selectOne(s -> s
                .where(RoleDynamicSqlSupport.role.role, SqlBuilder.isEqualTo(role))
                .and(RoleDynamicSqlSupport.systemId, SqlBuilder.isEqualTo(system))
                .limit(1));
        return roleModel.isPresent();
    }

    private void checkRoleAndUser(String userId, String role, String system) throws Exception {
        if (!userService.exist(userId)) {
            throw new Exception("用户不存在");
        }

        if (!existRole(role, system)) {
            throw new Exception("角色不存在");
        }
    }

    @Override
    public void grantRoleForUser(String userId, String role, String system) throws Exception {
        checkRoleAndUser(userId, role, system);
        final String roleName = Identify.getRoleName(Identify.ROLE, role);
        casbinService.grantRole(userId, roleName, system);
    }

    @Override
    public void revokeRoleForUser(String userId, String role, String system) throws Exception {
        checkRoleAndUser(userId, role, system);
        final String roleName = Identify.getRoleName(Identify.ROLE, role);
        casbinService.revokeRole(userId, roleName, system);
    }

    @Override
    public List<String> getUserRoles(String userId, String system) {
        return casbinService.getUserRoles(userId, system)
                .parallelStream()
                .map(Identify::getRawRoleName)
                .toList();
    }

    @Override
    public List<User> getRoleUsers(String role, String system) {
        List<Long> userIds = casbinService.getRoleUsers(role, system)
                .parallelStream()
                .map(Identify::getRawRoleName)
                .map(Long::valueOf)
                .toList();
        if (userIds.size() > 0) {
            return userMapper.select(u -> u.where(UserDynamicSqlSupport.id, SqlBuilder.isIn(userIds)));
        } else {
            return Collections.emptyList();
        }
    }

    @Override
    @Transactional
    @ShardingSphereTransactionType
    public ServiceResult<Void> delRole(String role, String system) throws Exception {
        ServiceResult.ServiceResultBuilder<Void> result = ServiceResult.builder();
        if (!existRole(role, system)) {
            return result.exception(MessageFormat.format("系统 {0} 角色 {1} 不存在", system, role)).build();
        }

        List<String> relation = casbinService.getRelationForRoleInSystem(role, system);
        log.info("{} 关联的用户/角色 {}", role, relation);
        if (relation.size() > 0) {
            return result.exception(MessageFormat.format("系统 {0} 角色 {1} 已有用户/角色关联", system, role)).build();
        }

        casbinService.delRole(role, system);

        roleMapper.delete((r) ->
                r.where(RoleDynamicSqlSupport.role.role, SqlBuilder.isEqualTo(role))
                        .and(RoleDynamicSqlSupport.systemId, SqlBuilder.isEqualTo(system))
        );
        return result.build();
    }

    @Override
    @Transactional
    @ShardingSphereTransactionType
    public ServiceResult<Void> addRole(String roleName, String role, String system, List<RoleRoutePermission> routePermissions) throws Exception {
        ServiceResult.ServiceResultBuilder<Void> result = ServiceResult.builder();
        if (existRole(role, system)) {
            return result.exception(MessageFormat.format("系统 {0} 角色 {1} 已存在", system, role))
                    .build();
        }

        Role r = new Role();
        r.setId(IdUtil.getSnowflakeNextId());
        r.setActive(true);
        r.setRole(role);
        r.setName(roleName);
        r.setSystemId(system);
        roleMapper.insert(r);

        ForkJoinPool pool = new ForkJoinPool(Runtime.getRuntime().availableProcessors() * 2);
        ForkJoinTask<Void> task = pool.submit(() -> {
            routePermissions.parallelStream()
                    .map(item -> new Permission(Identify.ROLE, role, system, item.getUrl(), item.getMethod()))
                    .forEach(casbinService::addRole);
            return null;
        });

        task.get();
        return result.build();
    }
}
