package cn.novalon.manage.gateway.service.impl;

import cn.novalon.manage.gateway.model.Permission;
import cn.novalon.manage.gateway.model.Role;
import cn.novalon.manage.gateway.model.User;
import cn.novalon.manage.gateway.service.PermissionService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

@Service
public class PermissionServiceImpl implements PermissionService {

    private static final Logger logger = LoggerFactory.getLogger(PermissionServiceImpl.class);
    
    private final WebClient webClient;
    private final String userServiceUrl;
    
    private final Map<Long, User> userCache = new ConcurrentHashMap<>();
    private final Map<Long, List<Role>> userRolesCache = new ConcurrentHashMap<>();
    private final Map<Long, Set<Permission>> userPermissionsCache = new ConcurrentHashMap<>();
    
    private final Map<Long, Long> userCacheTimestamp = new ConcurrentHashMap<>();
    private final Map<Long, Long> rolesCacheTimestamp = new ConcurrentHashMap<>();
    private final Map<Long, Long> permissionsCacheTimestamp = new ConcurrentHashMap<>();
    
    private static final long CACHE_EXPIRY_MS = 5 * 60 * 1000;

    public PermissionServiceImpl(WebClient.Builder webClientBuilder, 
                           @Value("${user.service.url:http://localhost:8084}") String userServiceUrl) {
        this.webClient = webClientBuilder.build();
        this.userServiceUrl = userServiceUrl;
    }

    @Override
    public User getUserById(Long userId) {
        if (userId == null) {
            return null;
        }
        
        Long cacheTime = userCacheTimestamp.get(userId);
        long currentTime = System.currentTimeMillis();
        
        if (cacheTime != null && (currentTime - cacheTime) < CACHE_EXPIRY_MS) {
            logger.debug("Returning cached user for userId: {}", userId);
            return userCache.get(userId);
        }
        
        try {
            logger.debug("Fetching user from service for userId: {}", userId);
            User user = webClient.get()
                    .uri(userServiceUrl + "/api/users/" + userId)
                    .retrieve()
                    .bodyToMono(User.class)
                    .block();
            
            if (user != null) {
                userCache.put(userId, user);
                userCacheTimestamp.put(userId, currentTime);
                logger.debug("Cached user for userId: {}", userId);
            }
            
            return user;
        } catch (Exception e) {
            logger.error("Error fetching user for userId: {}", userId, e);
            return userCache.get(userId);
        }
    }

    @Override
    public List<Role> getUserRoles(Long userId) {
        if (userId == null) {
            return Collections.emptyList();
        }
        
        Long cacheTime = rolesCacheTimestamp.get(userId);
        long currentTime = System.currentTimeMillis();
        
        if (cacheTime != null && (currentTime - cacheTime) < CACHE_EXPIRY_MS) {
            logger.debug("Returning cached roles for userId: {}", userId);
            return userRolesCache.getOrDefault(userId, Collections.emptyList());
        }
        
        try {
            logger.debug("Fetching roles from service for userId: {}", userId);
            Role[] roles = webClient.get()
                    .uri(userServiceUrl + "/api/users/" + userId + "/roles")
                    .retrieve()
                    .bodyToMono(Role[].class)
                    .block();
            
            List<Role> roleList = roles != null ? Arrays.asList(roles) : Collections.emptyList();
            userRolesCache.put(userId, roleList);
            rolesCacheTimestamp.put(userId, currentTime);
            logger.debug("Cached roles for userId: {}, count: {}", userId, roleList.size());
            
            return roleList;
        } catch (Exception e) {
            logger.error("Error fetching roles for userId: {}", userId, e);
            return userRolesCache.getOrDefault(userId, Collections.emptyList());
        }
    }

    @Override
    public Set<Permission> getUserPermissions(Long userId) {
        if (userId == null) {
            return Collections.emptySet();
        }
        
        Long cacheTime = permissionsCacheTimestamp.get(userId);
        long currentTime = System.currentTimeMillis();
        
        if (cacheTime != null && (currentTime - cacheTime) < CACHE_EXPIRY_MS) {
            logger.debug("Returning cached permissions for userId: {}", userId);
            return userPermissionsCache.getOrDefault(userId, Collections.emptySet());
        }
        
        try {
            logger.debug("Fetching permissions from service for userId: {}", userId);
            Permission[] permissions = webClient.get()
                    .uri(userServiceUrl + "/api/users/" + userId + "/permissions")
                    .retrieve()
                    .bodyToMono(Permission[].class)
                    .block();
            
            Set<Permission> permissionSet = permissions != null ? 
                    new HashSet<>(Arrays.asList(permissions)) : Collections.emptySet();
            userPermissionsCache.put(userId, permissionSet);
            permissionsCacheTimestamp.put(userId, currentTime);
            logger.debug("Cached permissions for userId: {}, count: {}", userId, permissionSet.size());
            
            return permissionSet;
        } catch (Exception e) {
            logger.error("Error fetching permissions for userId: {}", userId, e);
            return userPermissionsCache.getOrDefault(userId, Collections.emptySet());
        }
    }

    @Override
    public boolean hasPermission(Long userId, String path, String method) {
        if (userId == null) {
            logger.warn("UserId is null, denying access");
            return false;
        }
        
        Set<String> permissionPaths = getPermissionPaths(userId, method);
        
        for (String permissionPath : permissionPaths) {
            if (matchPath(permissionPath, path)) {
                logger.debug("Permission granted for userId: {}, path: {}, method: {}, matched permission: {}", 
                           userId, path, method, permissionPath);
                return true;
            }
        }
        
        logger.warn("Permission denied for userId: {}, path: {}, method: {}", userId, path, method);
        return false;
    }

    @Override
    public Set<String> getPermissionPaths(Long userId, String method) {
        Set<Permission> permissions = getUserPermissions(userId);
        
        return permissions.stream()
                .filter(p -> method.equalsIgnoreCase(p.getHttpMethod()))
                .map(Permission::getResourcePath)
                .collect(Collectors.toSet());
    }

    private boolean matchPath(String permissionPath, String requestPath) {
        if (permissionPath.equals(requestPath)) {
            return true;
        }
        
        if (permissionPath.endsWith("/**")) {
            String basePath = permissionPath.substring(0, permissionPath.length() - 3);
            return requestPath.startsWith(basePath);
        }
        
        if (permissionPath.endsWith("/*")) {
            String basePath = permissionPath.substring(0, permissionPath.length() - 2);
            return requestPath.startsWith(basePath) && 
                   !requestPath.substring(basePath.length() + 1).contains("/");
        }
        
        if (permissionPath.contains("*")) {
            String regex = permissionPath.replace("*", ".*");
            return requestPath.matches(regex);
        }
        
        return false;
    }

    public void clearCache(Long userId) {
        if (userId != null) {
            userCache.remove(userId);
            userRolesCache.remove(userId);
            userPermissionsCache.remove(userId);
            userCacheTimestamp.remove(userId);
            rolesCacheTimestamp.remove(userId);
            permissionsCacheTimestamp.remove(userId);
            logger.info("Cleared cache for userId: {}", userId);
        }
    }

    public void clearAllCache() {
        userCache.clear();
        userRolesCache.clear();
        userPermissionsCache.clear();
        userCacheTimestamp.clear();
        rolesCacheTimestamp.clear();
        permissionsCacheTimestamp.clear();
        logger.info("Cleared all permission cache");
    }
}