package com.spring.common.support;

import org.springframework.core.MethodParameter;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import com.spring.common.converter.CommHttpMessageConverter;
import com.spring.common.error.ErrorResponse;
import com.spring.common.error.ErrorRule;
import com.spring.common.error.GlobalExceptionHandler;
import com.spring.common.validation.CollectionValidator;
import com.spring.infra.security.error.SecurityExceptionHandler;

/**
 * API 응답을 래핑하는 전역 응답 처리기 클래스입니다.
 * 
 * <p>이 클래스는 Spring의 @RestControllerAdvice를 사용하여 특정 패키지 내의 모든
 * 컨트롤러에서 발생하는 응답을 처리합니다.</p>
 * 
 * <p>응답 본문을 ApiResponse 형식으로 변환하며, 오류 응답을 처리하는 기능을 제공합니다.</p>
 * 
 * @author mindol
 * @version 1.0
 */
@RestControllerAdvice(
    basePackages = "com.spring.domain",
    basePackageClasses = { GlobalExceptionHandler.class, SecurityExceptionHandler.class }
)
public class ResponseWrapper implements ResponseBodyAdvice<Object> {

    /**
     * 유효성 검사기를 나타내는 필드입니다.
     * 
     * <p>LocalValidatorFactoryBean을 사용하여 유효성 검사를 수행합니다.</p>
     */
    private final LocalValidatorFactoryBean validator;

    /**
     * ResponseWrapper 생성자입니다.
     * 
     * <p>주입된 LocalValidatorFactoryBean을 사용하여 ResponseWrapper를 초기화합니다.</p>
     * 
     * @param validator 유효성 검사기
     */
    public ResponseWrapper(LocalValidatorFactoryBean validator) {
        this.validator = validator;
    }

    /**
     * 데이터 바인딩 초기화 메서드입니다.
     * 
     * <p>WebDataBinder에 CollectionValidator를 추가하여 컬렉션 유효성 검사를 수행합니다.</p>
     * 
     * @param binder WebDataBinder 객체
     */
    @InitBinder
    public void initBinder(WebDataBinder binder) {
        binder.addValidators(new CollectionValidator(validator));
    }
    
    /**
     * 응답 본문을 처리할 수 있는지 여부를 확인합니다.
     * 
     * <p>CommHttpMessageConverter를 사용하여 응답 본문을 처리할 수 있는지 판단합니다.</p>
     * 
     * @param returnType 반환할 메서드의 MethodParameter
     * @param converterType 사용된 HttpMessageConverter의 클래스
     * @return 처리할 수 있는 경우 true, 그렇지 않으면 false
     */
    @Override
    public boolean supports(
        @NonNull MethodParameter returnType, 
        @NonNull Class<? extends HttpMessageConverter<?>> converterType
    ) {
        return CommHttpMessageConverter.class.isAssignableFrom(converterType);
    }

    /**
     * 응답 본문을 작성하기 전에 호출되는 메서드입니다.
     * 
     * <p>응답 본문이 ErrorResponse인 경우, ApiResponse 형식으로 변환하여 반환합니다.</p>
     * 
     * @param body 응답 본문
     * @param returnType 반환할 메서드의 MethodParameter
     * @param selectedContentType 선택된 미디어 타입
     * @param selectedConverterType 선택된 HttpMessageConverter의 클래스
     * @param request HTTP 요청 객체
     * @param response HTTP 응답 객체
     * @return 변환된 ApiResponse 객체
     */
    @Override
    public Object beforeBodyWrite(
        @Nullable Object body,
        @NonNull MethodParameter returnType,
        @NonNull MediaType selectedContentType,
        @NonNull Class<? extends HttpMessageConverter<?>> selectedConverterType,
        @NonNull ServerHttpRequest request,
        @NonNull ServerHttpResponse response
    ) {
        String path = request.getURI().getPath();

        if (body instanceof ErrorResponse) {
            ErrorResponse errorData = ((ErrorResponse) body);
            ErrorRule errorRule = errorData.getErrorRule();
            response.setStatusCode(errorRule.getStatus());

            return ApiResponse.builder()
                .statusCode(errorRule.getStatus().value())
                .path(path)
                .data(errorData.getErrors())
                .message(errorData.getCustomMessage() != null ? errorData.getCustomMessage() : errorRule.getMessage())
                .build();
        }

        return ApiResponse.builder()
            .statusCode(HttpStatus.OK.value())
            .path(path)
            .data(body)
            .build();
    }

}
