package com.kamco.cd.kamcoback.config.api;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.kamco.cd.kamcoback.auth.CustomUserDetails;
import com.kamco.cd.kamcoback.menu.dto.MenuDto;
import com.kamco.cd.kamcoback.menu.service.MenuService;
import com.kamco.cd.kamcoback.postgres.entity.AuditLogEntity;
import com.kamco.cd.kamcoback.postgres.repository.log.AuditLogRepository;
import jakarta.servlet.http.HttpServletRequest;
import java.util.LinkedHashMap;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
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.http.server.ServletServerHttpRequest;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import org.springframework.web.util.ContentCachingRequestWrapper;

/**
 * ApiResponseDto의 내장된 HTTP 상태 코드를 실제 HTTP 응답에 적용하는 Advice
 *
 * <p>createOK() → 201 CREATED ok() → 200 OK deleteOk() → 204 NO_CONTENT
 */
@RestControllerAdvice
public class ApiResponseAdvice implements ResponseBodyAdvice<Object> {

  private final AuditLogRepository auditLogRepository;
  private final MenuService menuService;

  @Autowired private ObjectMapper objectMapper;

  public ApiResponseAdvice(AuditLogRepository auditLogRepository, MenuService menuService) {
    this.auditLogRepository = auditLogRepository;
    this.menuService = menuService;
  }

  @Override
  public boolean supports(
      MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
    // ApiResponseDto를 반환하는 경우에만 적용
    return returnType.getParameterType().equals(ApiResponseDto.class);
  }

  @Override
  public Object beforeBodyWrite(
      Object body,
      MethodParameter returnType,
      MediaType selectedContentType,
      Class<? extends HttpMessageConverter<?>> selectedConverterType,
      ServerHttpRequest request,
      ServerHttpResponse response) {

    HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest();
    ContentCachingRequestWrapper contentWrapper = null;
    if (servletRequest instanceof ContentCachingRequestWrapper wrapper) {
      contentWrapper = wrapper;
    }

    if (body instanceof ApiResponseDto<?> apiResponse) {
      response.setStatusCode(apiResponse.getHttpStatus());

      String ip = ApiLogFunction.getClientIp(servletRequest);
      Long userid = null;

      if (servletRequest.getUserPrincipal() instanceof UsernamePasswordAuthenticationToken auth
          && auth.getPrincipal() instanceof CustomUserDetails customUserDetails) {
        userid = customUserDetails.getMember().getId();
      }

      String requestBody;
      // 멀티파트 요청은 바디 로깅을 생략 (파일 바이너리로 인한 문제 예방)
      MediaType reqContentType = null;
      try {
        String ct = servletRequest.getContentType();
        reqContentType = ct != null ? MediaType.valueOf(ct) : null;
      } catch (Exception ignored) {
      }
      if (reqContentType != null && MediaType.MULTIPART_FORM_DATA.includes(reqContentType)) {
        requestBody = "(multipart omitted)";
      } else {
        requestBody = ApiLogFunction.getRequestBody(servletRequest, contentWrapper);
        requestBody = maskSensitiveFields(requestBody);
      }

      List<?> list = menuService.getFindAll();
      List<MenuDto.Basic> result =
          list.stream()
              .map(
                  item -> {
                    if (item instanceof LinkedHashMap<?, ?> map) {
                      return objectMapper.convertValue(map, MenuDto.Basic.class);
                    } else if (item instanceof MenuDto.Basic dto) {
                      return dto;
                    } else {
                      throw new IllegalStateException("Unsupported cache type: " + item.getClass());
                    }
                  })
              .toList();

      AuditLogEntity log =
          new AuditLogEntity(
              userid,
              ApiLogFunction.getEventType(servletRequest),
              ApiLogFunction.isSuccessFail(apiResponse),
              ApiLogFunction.getUriMenuInfo(result, servletRequest.getRequestURI()),
              ip,
              servletRequest.getRequestURI(),
              requestBody,
              apiResponse.getErrorLogUid());
      auditLogRepository.save(log);
    }

    return body;
  }

  /**
   * 마스킹
   *
   * @param json
   * @return
   */
  private String maskSensitiveFields(String json) {
    if (json == null) {
      return null;
    }

    // password 마스킹
    json = json.replaceAll("\"password\"\\s*:\\s*\"[^\"]*\"", "\"password\":\"****\"");

    // 토큰 마스킹
    json = json.replaceAll("\"accessToken\"\\s*:\\s*\"[^\"]*\"", "\"accessToken\":\"****\"");
    json = json.replaceAll("\"refreshToken\"\\s*:\\s*\"[^\"]*\"", "\"refreshToken\":\"****\"");

    return json;
  }
}
