package com.saas.admin.service;

import com.saas.admin.entity.Permission;
import com.saas.admin.entity.Role;
import com.saas.admin.entity.RolePermission;
import com.saas.admin.entity.User;
import com.saas.admin.entity.UserRole;
import com.saas.admin.repository.PermissionRepository;
import com.saas.admin.repository.RolePermissionRepository;
import com.saas.admin.repository.RoleRepository;
import com.saas.admin.repository.UserRoleRepository;
import com.saas.shared.audit.Auditable;
import com.saas.shared.security.PermissionCacheService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

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

/**
 * Service for managing Roles in the RBAC system
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class RoleService {
    
    private final RoleRepository roleRepository;
    private final PermissionRepository permissionRepository;
    private final RolePermissionRepository rolePermissionRepository;
    private final UserRoleRepository userRoleRepository;
    private final PermissionCacheService permissionCacheService;
    
    /**
     * Create a new role
     */
    @Transactional
    @Auditable(action = "CREATE_ROLE", entityType = "ROLE")
    public Role createRole(String name, String description, boolean isSystem) {
        log.info("📝 Creating role: {}", name);
        
        if (roleRepository.existsByName(name)) {
            throw new IllegalArgumentException("Role with name '" + name + "' already exists");
        }
        
        Role role = Role.builder()
                .name(name.toUpperCase())
                .description(description)
                .isActive(true)
                .isSystem(isSystem)
                .build();
        
        Role saved = roleRepository.save(role);
        log.info("✅ Role created with ID: {}", saved.getId());
        
        return saved;
    }
    
    /**
     * Get role by ID
     */
    @Transactional(readOnly = true)
    public Optional<Role> getRoleById(Long id) {
        return roleRepository.findById(id);
    }
    
    /**
     * Get role by name
     */
    @Transactional(readOnly = true)
    public Optional<Role> getRoleByName(String name) {
        return roleRepository.findByName(name);
    }
    
    /**
     * Get role by ID with permissions eagerly loaded
     */
    @Transactional(readOnly = true)
    public Optional<Role> getRoleByIdWithPermissions(Long id) {
        return roleRepository.findByIdWithPermissions(id);
    }
    
    /**
     * Get all roles
     */
    @Transactional(readOnly = true)
    public List<Role> getAllRoles() {
        return roleRepository.findAll();
    }
    
    /**
     * Get all active roles
     */
    @Transactional(readOnly = true)
    public List<Role> getAllActiveRoles() {
        return roleRepository.findByIsActiveTrue();
    }
    
    /**
     * Update role
     */
    @Transactional
    public Role updateRole(Long id, String description, Boolean isActive) {
        log.info("✏️ Updating role ID: {}", id);
        
        Role role = roleRepository.findById(id)
                .orElseThrow(() -> new IllegalArgumentException("Role not found with ID: " + id));
        
        if (description != null) {
            role.setDescription(description);
        }
        
        if (isActive != null) {
            role.setIsActive(isActive);
        }
        
        Role updated = roleRepository.save(role);
        log.info("✅ Role updated");
        
        return updated;
    }
    
    /**
     * Delete role (only if not system role)
     */
    @Transactional
    @Auditable(action = "DELETE_ROLE", entityType = "ROLE")
    public void deleteRole(Long id) {
        log.info("🗑️ Deleting role ID: {}", id);
        
        Role role = roleRepository.findById(id)
                .orElseThrow(() -> new IllegalArgumentException("Role not found with ID: " + id));
        
        if (role.getIsSystem()) {
            throw new IllegalStateException("Cannot delete system role: " + role.getName());
        }
        
        // Check if role is assigned to any users
        long userCount = userRoleRepository.countByRole(role);
        if (userCount > 0) {
            throw new IllegalStateException("Cannot delete role assigned to " + userCount + " users. Remove user assignments first.");
        }
        
        roleRepository.delete(role);
        log.info("✅ Role deleted");
    }
    
    /**
     * Assign permission to role
     */
    @Transactional
    public void assignPermissionToRole(Long roleId, Long permissionId) {
        log.info("➕ Assigning permission {} to role {}", permissionId, roleId);
        
        Role role = roleRepository.findById(roleId)
                .orElseThrow(() -> new IllegalArgumentException("Role not found with ID: " + roleId));
        
        Permission permission = permissionRepository.findById(permissionId)
                .orElseThrow(() -> new IllegalArgumentException("Permission not found with ID: " + permissionId));
        
        // Check if already assigned
        if (rolePermissionRepository.existsByRoleAndPermission(role, permission)) {
            log.warn("⚠️ Permission already assigned to role");
            return;
        }
        
        role.addPermission(permission);
        roleRepository.save(role);
        
        permissionCacheService.invalidateByRole(roleId);
        
        log.info("Permission assigned to role");
    }
    
    /**
     * Remove permission from role
     */
    @Transactional
    @Auditable(action = "REMOVE_PERMISSION_FROM_ROLE", entityType = "ROLE")
    public void removePermissionFromRole(Long roleId, Long permissionId) {
        log.info("Removing permission {} from role {}", permissionId, roleId);
        
        Role role = roleRepository.findById(roleId)
                .orElseThrow(() -> new IllegalArgumentException("Role not found with ID: " + roleId));
        
        Permission permission = permissionRepository.findById(permissionId)
                .orElseThrow(() -> new IllegalArgumentException("Permission not found with ID: " + permissionId));
        
        role.removePermission(permission);
        roleRepository.save(role);
        
        permissionCacheService.invalidateByRole(roleId);
        
        log.info("Permission removed from role");
    }
    
    /**
     * Assign multiple permissions to role
     */
    @Transactional
    @Auditable(action = "ASSIGN_PERMISSIONS", entityType = "ROLE")
    public void assignPermissionsToRole(Long roleId, List<Long> permissionIds) {
        log.info("➕ Assigning {} permissions to role {}", permissionIds.size(), roleId);
        
        Role role = roleRepository.findById(roleId)
                .orElseThrow(() -> new IllegalArgumentException("Role not found with ID: " + roleId));
        
        List<Permission> permissions = permissionRepository.findByIdIn(permissionIds);
        
        if (permissions.size() != permissionIds.size()) {
            throw new IllegalArgumentException("Some permissions not found");
        }
        
        for (Permission permission : permissions) {
            if (!rolePermissionRepository.existsByRoleAndPermission(role, permission)) {
                role.addPermission(permission);
            }
        }
        
        roleRepository.save(role);
        
        permissionCacheService.invalidateByRole(roleId);
        
        log.info("{} permissions assigned to role", permissions.size());
    }
    
    /**
     * Get all permissions for a role
     */
    @Transactional(readOnly = true)
    public List<String> getRolePermissions(Long roleId) {
        return rolePermissionRepository.findPermissionStringsByRoleId(roleId);
    }
    
    /**
     * Assign role to user
     */
    @Transactional
    public void assignRoleToUser(User user, Role role, String assignedBy) {
        log.info("➕ Assigning role {} to user {}", role.getName(), user.getEmail());
        
        // Check if already assigned
        if (userRoleRepository.existsByUserAndRole(user, role)) {
            log.warn("⚠️ Role already assigned to user");
            return;
        }
        
        UserRole userRole = UserRole.builder()
                .user(user)
                .role(role)
                .assignedBy(assignedBy)
                .build();
        
        userRoleRepository.save(userRole);
        
        permissionCacheService.invalidateUser(user.getId());
        
        log.info("Role assigned to user");
    }
    
    /**
     * Remove role from user
     */
    @Transactional
    public void removeRoleFromUser(User user, Role role) {
        log.info("➖ Removing role {} from user {}", role.getName(), user.getEmail());
        
        userRoleRepository.findByUserAndRole(user, role)
                .ifPresent(userRoleRepository::delete);
        
        permissionCacheService.invalidateUser(user.getId());
        
        log.info("Role removed from user");
    }
}
