package de.avatic.lcc.config.filter;

import de.avatic.lcc.config.LccOidcUser;
import de.avatic.lcc.service.users.UserAuthorityCacheService;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Profile;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;

@Component
@Profile("!dev & !test")
public class AuthorityRefreshFilter extends OncePerRequestFilter {

    private static final Logger log = LoggerFactory.getLogger(AuthorityRefreshFilter.class);
    private static final String AUTHORITIES_CHECKED_ATTR = "authorities_checked";

    private final UserAuthorityCacheService userAuthorityService;

    public AuthorityRefreshFilter(UserAuthorityCacheService userAuthorityService) {
        this.userAuthorityService = userAuthorityService;
    }

    @Override
    protected void doFilterInternal(@NotNull HttpServletRequest request,
                                    @NotNull HttpServletResponse response,
                                    @NotNull FilterChain filterChain) throws ServletException, IOException {

        if (request.getAttribute(AUTHORITIES_CHECKED_ATTR) == null) {
            refreshAuthorities();
            request.setAttribute(AUTHORITIES_CHECKED_ATTR, true);
        }

        filterChain.doFilter(request, response);
    }

    private void refreshAuthorities() {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();

        if (auth != null && auth.getPrincipal() instanceof LccOidcUser oidcUser) {
            Integer userId = oidcUser.getSqlUserId();

            if (userId != null) {
                Set<GrantedAuthority> cachedAuthorities = userAuthorityService.getUserAuthorities(userId);

                if (!authoritiesEqual(cachedAuthorities, oidcUser.getAuthorities())) {
                    log.debug("Authorities changed for user {}, updating SecurityContext", userId);
                    updateSecurityContext(auth, oidcUser, cachedAuthorities);
                }
            }
        }
    }

    private boolean authoritiesEqual(Set<GrantedAuthority> cached,
                                     java.util.Collection<? extends GrantedAuthority> current) {
        Set<String> cachedRoles = cached.stream()
                .map(GrantedAuthority::getAuthority)
                .filter(auth -> auth.startsWith("ROLE_"))
                .collect(Collectors.toSet());

        Set<String> currentRoles = current.stream()
                .map(GrantedAuthority::getAuthority)
                .filter(auth -> auth.startsWith("ROLE_"))
                .collect(Collectors.toSet());

        return cachedRoles.equals(currentRoles);
    }

    private void updateSecurityContext(Authentication auth, LccOidcUser oidcUser,
                                       Set<GrantedAuthority> newRoleAuthorities) {
        Set<GrantedAuthority> updatedAuthorities = new HashSet<>();

        oidcUser.getAuthorities().stream()
                .filter(authority -> authority.getAuthority().startsWith("SCOPE_"))
                .forEach(updatedAuthorities::add);

        updatedAuthorities.addAll(newRoleAuthorities);

        LccOidcUser updatedUser = new LccOidcUser(
                updatedAuthorities,
                oidcUser.getIdToken(),
                oidcUser.getUserInfo(),
                "preferred_username",
                oidcUser.getSqlUserId()
        );

        if (auth instanceof OAuth2AuthenticationToken oauthToken) {
            Authentication newAuth = new OAuth2AuthenticationToken(
                    updatedUser,
                    updatedAuthorities,
                    oauthToken.getAuthorizedClientRegistrationId()
            );

            SecurityContextHolder.getContext().setAuthentication(newAuth);
        }
    }
}