package com.cloudhandson.vpdbackoffice.service;

import com.cloudhandson.vpdbackoffice.domain.effective.EffectiveMatrixView;
import com.cloudhandson.vpdbackoffice.domain.effective.GroupEffectiveAccessView;
import com.cloudhandson.vpdbackoffice.domain.effective.RoleEffectiveImpactView;
import com.cloudhandson.vpdbackoffice.domain.effective.UserEffectiveAccessView;
import com.cloudhandson.vpdbackoffice.domain.group.AppGroup;
import com.cloudhandson.vpdbackoffice.domain.group.GroupRoleView;
import com.cloudhandson.vpdbackoffice.domain.group.GroupUserView;
import com.cloudhandson.vpdbackoffice.domain.permission.AppRole;
import com.cloudhandson.vpdbackoffice.domain.permission.PermissionView;
import com.cloudhandson.vpdbackoffice.domain.user.AppUser;
import com.cloudhandson.vpdbackoffice.domain.user.UserRoleView;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.springframework.stereotype.Service;

@Service
public class EffectiveMatrixService {

  private final UserService userService;
  private final GroupService groupService;
  private final PermissionService permissionService;

  public EffectiveMatrixService(
      UserService userService,
      GroupService groupService,
      PermissionService permissionService
  ) {
    this.userService = userService;
    this.groupService = groupService;
    this.permissionService = permissionService;
  }

  public EffectiveMatrixView matrix() {
    List<AppUser> users = userService.findAll();
    List<AppGroup> groups = groupService.findAll();
    List<AppRole> roles = permissionService.findRoles();
    List<UserRoleView> userRoles = userService.findUserRoles();
    List<GroupUserView> groupUsers = groupService.findGroupUsers();
    List<GroupRoleView> groupRoles = groupService.findGroupRoles();
    List<PermissionView> permissions = permissionService.findPermissionViews();

    Map<Long, Set<Long>> directRoleIdsByUser = directRoleIdsByUser(userRoles);
    Map<Long, List<String>> directRoleNamesByUser = directRoleNamesByUser(userRoles);
    Map<Long, Set<Long>> groupIdsByUser = groupIdsByUser(groupUsers);
    Map<Long, List<String>> groupNamesByUser = groupNamesByUser(groupUsers);
    Map<Long, List<String>> userNamesByGroup = userNamesByGroup(groupUsers);
    Map<Long, Set<Long>> roleIdsByGroup = roleIdsByGroup(groupRoles);
    Map<Long, List<String>> roleNamesByGroup = roleNamesByGroup(groupRoles);
    Map<Long, List<String>> groupNamesByRole = groupNamesByRole(groupRoles);
    Map<Long, List<String>> directUserNamesByRole = directUserNamesByRole(userRoles);
    Map<Long, List<PermissionView>> permissionsByRole = permissionsByRole(permissions);
    Map<Long, String> roleNameById = roleNameById(roles);

    List<UserEffectiveAccessView> userViews = users.stream()
        .map(user -> userView(
            user,
            directRoleIdsByUser,
            directRoleNamesByUser,
            groupIdsByUser,
            groupNamesByUser,
            roleIdsByGroup,
            roleNameById,
            permissionsByRole
        ))
        .toList();

    List<GroupEffectiveAccessView> groupViews = groups.stream()
        .map(group -> groupView(group, userNamesByGroup, roleIdsByGroup, roleNamesByGroup, permissionsByRole))
        .toList();

    List<RoleEffectiveImpactView> roleViews = roles.stream()
        .map(role -> roleView(role, directUserNamesByRole, groupNamesByRole, groupUsers, groupRoles, permissionsByRole))
        .toList();

    return new EffectiveMatrixView(userViews, groupViews, roleViews, permissions.size());
  }

  private UserEffectiveAccessView userView(
      AppUser user,
      Map<Long, Set<Long>> directRoleIdsByUser,
      Map<Long, List<String>> directRoleNamesByUser,
      Map<Long, Set<Long>> groupIdsByUser,
      Map<Long, List<String>> groupNamesByUser,
      Map<Long, Set<Long>> roleIdsByGroup,
      Map<Long, String> roleNameById,
      Map<Long, List<PermissionView>> permissionsByRole
  ) {
    Set<Long> directRoleIds = directRoleIdsByUser.getOrDefault(user.userId(), Set.of());
    Set<Long> inheritedRoleIds = new LinkedHashSet<>();
    for (Long groupId : groupIdsByUser.getOrDefault(user.userId(), Set.of())) {
      inheritedRoleIds.addAll(roleIdsByGroup.getOrDefault(groupId, Set.of()));
    }

    Set<Long> effectiveRoleIds = new LinkedHashSet<>(directRoleIds);
    effectiveRoleIds.addAll(inheritedRoleIds);

    return new UserEffectiveAccessView(
        user.userId(),
        user.username(),
        user.empNo(),
        user.deptCode(),
        user.active(),
        directRoleNamesByUser.getOrDefault(user.userId(), List.of()),
        groupNamesByUser.getOrDefault(user.userId(), List.of()),
        names(inheritedRoleIds, roleNameById),
        names(effectiveRoleIds, roleNameById),
        permissionCount(effectiveRoleIds, permissionsByRole),
        objectNames(effectiveRoleIds, permissionsByRole)
    );
  }

  private GroupEffectiveAccessView groupView(
      AppGroup group,
      Map<Long, List<String>> userNamesByGroup,
      Map<Long, Set<Long>> roleIdsByGroup,
      Map<Long, List<String>> roleNamesByGroup,
      Map<Long, List<PermissionView>> permissionsByRole
  ) {
    Set<Long> roleIds = roleIdsByGroup.getOrDefault(group.groupId(), Set.of());
    return new GroupEffectiveAccessView(
        group.groupId(),
        group.groupCode(),
        group.groupName(),
        group.active(),
        userNamesByGroup.getOrDefault(group.groupId(), List.of()),
        roleNamesByGroup.getOrDefault(group.groupId(), List.of()),
        permissionCount(roleIds, permissionsByRole),
        objectNames(roleIds, permissionsByRole)
    );
  }

  private RoleEffectiveImpactView roleView(
      AppRole role,
      Map<Long, List<String>> directUserNamesByRole,
      Map<Long, List<String>> groupNamesByRole,
      List<GroupUserView> groupUsers,
      List<GroupRoleView> groupRoles,
      Map<Long, List<PermissionView>> permissionsByRole
  ) {
    Set<Long> roleIds = Set.of(role.roleId());
    Set<String> inheritedUsers = new LinkedHashSet<>();
    Set<Long> roleGroupIds = new LinkedHashSet<>();
    for (GroupRoleView groupRole : groupRoles) {
      if (groupRole.roleId() == role.roleId()) {
        roleGroupIds.add(groupRole.groupId());
      }
    }
    for (GroupUserView groupUser : groupUsers) {
      if (roleGroupIds.contains(groupUser.groupId())) {
        inheritedUsers.add(groupUser.username());
      }
    }

    Set<String> affectedUsers = new LinkedHashSet<>(directUserNamesByRole.getOrDefault(role.roleId(), List.of()));
    affectedUsers.addAll(inheritedUsers);

    return new RoleEffectiveImpactView(
        role.roleId(),
        role.roleName(),
        role.maxSensitivityLevel(),
        directUserNamesByRole.getOrDefault(role.roleId(), List.of()),
        groupNamesByRole.getOrDefault(role.roleId(), List.of()),
        new ArrayList<>(inheritedUsers),
        new ArrayList<>(affectedUsers),
        permissionCount(roleIds, permissionsByRole),
        objectNames(roleIds, permissionsByRole)
    );
  }

  private Map<Long, Set<Long>> directRoleIdsByUser(List<UserRoleView> userRoles) {
    Map<Long, Set<Long>> result = new LinkedHashMap<>();
    for (UserRoleView view : userRoles) {
      result.computeIfAbsent(view.userId(), key -> new LinkedHashSet<>()).add(view.roleId());
    }
    return result;
  }

  private Map<Long, List<String>> directRoleNamesByUser(List<UserRoleView> userRoles) {
    Map<Long, Set<String>> result = new LinkedHashMap<>();
    for (UserRoleView view : userRoles) {
      result.computeIfAbsent(view.userId(), key -> new LinkedHashSet<>()).add(view.roleName());
    }
    return toListMap(result);
  }

  private Map<Long, Set<Long>> groupIdsByUser(List<GroupUserView> groupUsers) {
    Map<Long, Set<Long>> result = new LinkedHashMap<>();
    for (GroupUserView view : groupUsers) {
      result.computeIfAbsent(view.userId(), key -> new LinkedHashSet<>()).add(view.groupId());
    }
    return result;
  }

  private Map<Long, List<String>> groupNamesByUser(List<GroupUserView> groupUsers) {
    Map<Long, Set<String>> result = new LinkedHashMap<>();
    for (GroupUserView view : groupUsers) {
      result.computeIfAbsent(view.userId(), key -> new LinkedHashSet<>()).add(groupLabel(view.groupCode(), view.groupName()));
    }
    return toListMap(result);
  }

  private Map<Long, List<String>> userNamesByGroup(List<GroupUserView> groupUsers) {
    Map<Long, Set<String>> result = new LinkedHashMap<>();
    for (GroupUserView view : groupUsers) {
      result.computeIfAbsent(view.groupId(), key -> new LinkedHashSet<>()).add(view.username());
    }
    return toListMap(result);
  }

  private Map<Long, Set<Long>> roleIdsByGroup(List<GroupRoleView> groupRoles) {
    Map<Long, Set<Long>> result = new LinkedHashMap<>();
    for (GroupRoleView view : groupRoles) {
      result.computeIfAbsent(view.groupId(), key -> new LinkedHashSet<>()).add(view.roleId());
    }
    return result;
  }

  private Map<Long, List<String>> roleNamesByGroup(List<GroupRoleView> groupRoles) {
    Map<Long, Set<String>> result = new LinkedHashMap<>();
    for (GroupRoleView view : groupRoles) {
      result.computeIfAbsent(view.groupId(), key -> new LinkedHashSet<>()).add(view.roleName());
    }
    return toListMap(result);
  }

  private Map<Long, List<String>> groupNamesByRole(List<GroupRoleView> groupRoles) {
    Map<Long, Set<String>> result = new LinkedHashMap<>();
    for (GroupRoleView view : groupRoles) {
      result.computeIfAbsent(view.roleId(), key -> new LinkedHashSet<>()).add(groupLabel(view.groupCode(), view.groupName()));
    }
    return toListMap(result);
  }

  private Map<Long, List<String>> directUserNamesByRole(List<UserRoleView> userRoles) {
    Map<Long, Set<String>> result = new LinkedHashMap<>();
    for (UserRoleView view : userRoles) {
      result.computeIfAbsent(view.roleId(), key -> new LinkedHashSet<>()).add(view.username());
    }
    return toListMap(result);
  }

  private Map<Long, List<PermissionView>> permissionsByRole(List<PermissionView> permissions) {
    Map<Long, List<PermissionView>> result = new LinkedHashMap<>();
    for (PermissionView permission : permissions) {
      result.computeIfAbsent(permission.roleId(), key -> new ArrayList<>()).add(permission);
    }
    return result;
  }

  private Map<Long, String> roleNameById(List<AppRole> roles) {
    Map<Long, String> result = new LinkedHashMap<>();
    for (AppRole role : roles) {
      result.put(role.roleId(), role.roleName());
    }
    return result;
  }

  private List<String> names(Set<Long> ids, Map<Long, String> nameById) {
    return ids.stream()
        .map(nameById::get)
        .filter(name -> name != null && !name.isBlank())
        .toList();
  }

  private int permissionCount(Set<Long> roleIds, Map<Long, List<PermissionView>> permissionsByRole) {
    return roleIds.stream()
        .mapToInt(roleId -> permissionsByRole.getOrDefault(roleId, List.of()).size())
        .sum();
  }

  private List<String> objectNames(Set<Long> roleIds, Map<Long, List<PermissionView>> permissionsByRole) {
    Set<String> result = new LinkedHashSet<>();
    for (Long roleId : roleIds) {
      for (PermissionView permission : permissionsByRole.getOrDefault(roleId, List.of())) {
        result.add(permission.objectName() + " / " + permission.permissionEffect());
      }
    }
    return new ArrayList<>(result);
  }

  private Map<Long, List<String>> toListMap(Map<Long, Set<String>> source) {
    Map<Long, List<String>> result = new LinkedHashMap<>();
    source.forEach((key, value) -> result.put(key, new ArrayList<>(value)));
    return result;
  }

  private String groupLabel(String groupCode, String groupName) {
    return groupCode + " / " + groupName;
  }
}
