package org.brapi.test.BrAPITestServer.system.security;

import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.brapi.test.BrAPITestServer.auth.AuthDetails;
import org.brapi.test.BrAPITestServer.system.entity.SysUserEntity;
import org.brapi.test.BrAPITestServer.system.service.TokenService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.List;

@Component
public class SystemJwtAuthFilter extends OncePerRequestFilter {

    private static final Logger log = LoggerFactory.getLogger(SystemJwtAuthFilter.class);
    private static final String ANONYMOUS_KEY = "anonymousKey";

    private final TokenService tokenService;
    private final boolean authEnabled;

    public SystemJwtAuthFilter(
            TokenService tokenService,
            @Value("${security.enabled:true}") boolean authEnabled) {
        this.tokenService = tokenService;
        this.authEnabled = authEnabled;
    }

    @Override
    protected boolean shouldNotFilter(HttpServletRequest request) {
        if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
            return true;
        }
        String path = request.getRequestURI();
        return path.startsWith("/auth/");
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        if (!authEnabled) {
            setAuthenticatedUser("anonymousUser", Long.MAX_VALUE, true);
            filterChain.doFilter(request, response);
            return;
        }

        String header = request.getHeader("Authorization");
        if (header == null || !header.startsWith("Bearer ")) {
            setAnonymousUser();
            filterChain.doFilter(request, response);
            return;
        }

        String token = header.substring("Bearer ".length()).trim();
        try {
            SysUserEntity user = tokenService.resolveActiveUser(token)
                    .orElseThrow(() -> new JWTVerificationException("Session invalid or expired"));
            var decoded = tokenService.verifyToken(token);
            Date expiresAt = decoded.getExpiresAt();
            long expirationMillis = expiresAt != null ? expiresAt.getTime() : Long.MAX_VALUE;
            setAuthenticatedUser(user.getId().toString(), expirationMillis, false);
            filterChain.doFilter(request, response);
        } catch (TokenExpiredException ex) {
            writeAuthError(response, HttpStatus.FORBIDDEN, "Token expired");
        } catch (JWTVerificationException ex) {
            writeAuthError(response, HttpStatus.UNAUTHORIZED,
                    ex.getMessage() != null ? ex.getMessage() : "Invalid token signature");
        } catch (Exception ex) {
            log.error("Authentication failed", ex);
            writeAuthError(response, HttpStatus.UNAUTHORIZED, "Session invalid or expired");
        }
    }

    private void setAnonymousUser() {
        AnonymousAuthenticationToken authentication = new AnonymousAuthenticationToken(
                ANONYMOUS_KEY,
                "anonymousUser",
                List.of(new SimpleGrantedAuthority("ROLE_ANONYMOUS")));
        AuthDetails details = new AuthDetails();
        details.setUserId("anonymousUser");
        details.setExpirationTimestamp(Long.MAX_VALUE);
        details.addRole("ROLE_ANONYMOUS");
        authentication.setDetails(details);
        SecurityContextHolder.getContext().setAuthentication(authentication);
    }

    private void setAuthenticatedUser(String userId, long expirationMillis, boolean grantAdmin) {
        List<SimpleGrantedAuthority> authorities = grantAdmin
                ? List.of(
                        new SimpleGrantedAuthority("ROLE_USER"),
                        new SimpleGrantedAuthority("ROLE_ADMIN"),
                        new SimpleGrantedAuthority("ROLE_ANONYMOUS"))
                : List.of(new SimpleGrantedAuthority("ROLE_USER"));

        UsernamePasswordAuthenticationToken authentication =
                new UsernamePasswordAuthenticationToken(userId, null, authorities);
        AuthDetails details = new AuthDetails();
        details.setUserId(userId);
        details.setExpirationTimestamp(expirationMillis);
        details.addRole("ROLE_USER");
        if (grantAdmin) {
            details.addRole("ROLE_ADMIN");
            details.addRole("ROLE_ANONYMOUS");
        }
        authentication.setDetails(details);
        SecurityContextHolder.getContext().setAuthentication(authentication);
    }

    private void writeAuthError(HttpServletResponse response, HttpStatus status, String message) throws IOException {
        response.setStatus(status.value());
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setCharacterEncoding(StandardCharsets.UTF_8.name());
        String body = "{\"message\":\"" + escapeJson(message) + "\",\"detail\":\"" + escapeJson(message) + "\"}";
        response.getWriter().write(body);
    }

    private String escapeJson(String value) {
        return value.replace("\\", "\\\\").replace("\"", "\\\"");
    }
}
