package com.spring.infra.security.handler;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerExceptionResolver;

import com.spring.infra.security.config.PermittedURI;

import lombok.RequiredArgsConstructor;

/**
 * 인증되지 않은 접근을 처리하는 진입점 클래스입니다.
 * 
 * <p>이 클래스는 Spring Security의 AuthenticationEntryPoint 인터페이스를 구현하여,
 * 인증되지 않은 사용자가 보호된 리소스에 접근할 때 적절한 응답을 반환합니다.</p>
 * 
 * <p>API 요청과 일반 요청을 구분하여 각각에 대해 적절한 처리 로직을 수행합니다.</p>
 * 
 * @author mindol
 * @version 1.0
 */
@Component
@RequiredArgsConstructor
public class SecurityAuthenticationEntryPoint implements AuthenticationEntryPoint {

    /**
     * 예외 속성 이름.
     */
    private static final String EXCEPTION_ATTRIBUTE = "exception";

    /**
     * 예외 처리 리졸버.
     * 
     * <p>Spring의 HandlerExceptionResolver를 사용하여 예외를 처리합니다.</p>
     */
    @Qualifier("handlerExceptionResolver")
    private final HandlerExceptionResolver resolver;

    /**
     * HTTP 요청의 엔드포인트 존재 여부를 확인하는 체크 클래스.
     */
    private final HttpRequestEndpointChecker endpointChecker;

    /**
     * 인증되지 않은 사용자가 보호된 리소스에 접근할 때 호출됩니다.
     * 
     * <p>요청된 엔드포인트가 존재하지 않으면 404 Not Found 응답을 반환하고,
     * API 요청인 경우에는 예외를 처리하여 적절한 응답을 반환합니다.</p>
     * 
     * @param request HTTP 요청 객체
     * @param response HTTP 응답 객체
     * @param authException 인증 예외
     * @throws IOException 입출력 예외
     * @throws ServletException 서블릿 예외
     */
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response,
            AuthenticationException authException) throws IOException, ServletException {
        if (!endpointChecker.isEndpointExist(request)) {
            // 요청된 엔드포인트가 존재하지 않는 경우 404 응답을 반환합니다.
            response.sendError(HttpServletResponse.SC_NOT_FOUND);
        } else if (isApiRequest(request)) {
            // API 요청인 경우 예외를 처리합니다.
            handleApiRequest(request, response, authException);
        } else {
            // 일반 요청인 경우 루트 URI로 리다이렉트합니다.
            response.sendRedirect(PermittedURI.ROOT_URI.getUri());
        }
    }

    /**
     * 요청이 API 요청인지 확인합니다.
     * 
     * <p>HTTP 요청의 Accept 헤더를 검사하여 JSON 형식의 응답을 요구하는지 확인합니다.</p>
     * 
     * @param request HTTP 요청 객체
     * @return API 요청이면 true, 그렇지 않으면 false
     */
    private boolean isApiRequest(HttpServletRequest request) {
        String accept = request.getHeader(HttpHeaders.ACCEPT);
        return accept != null && accept.contains(MediaType.APPLICATION_JSON_VALUE);
    }

    /**
     * API 요청에 대한 예외를 처리합니다.
     * 
     * <p>인증 예외가 발생한 경우 적절한 응답을 생성하여 클라이언트에 반환합니다.</p>
     * 
     * @param request HTTP 요청 객체
     * @param response HTTP 응답 객체
     * @param authException 인증 예외
     */
    private void handleApiRequest(HttpServletRequest request, HttpServletResponse response,
            AuthenticationException authException) {
        if (request.getAttribute(EXCEPTION_ATTRIBUTE) != null) {
            // 요청 속성에 예외가 설정된 경우 해당 예외를 처리합니다.
            resolver.resolveException(request, response, null,
                    (Exception) request.getAttribute(EXCEPTION_ATTRIBUTE));
        } else {
            // 인증 예외를 처리합니다.
            resolver.resolveException(request, response, null, authException);
        }
    }
}
