package com.bio.bio_backend.global.filter;

import java.io.IOException;

import com.bio.bio_backend.domain.base.member.dto.MemberDto;
import com.bio.bio_backend.global.utils.HttpUtils;

import org.springframework.http.MediaType;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.filter.OncePerRequestFilter;

import com.fasterxml.jackson.databind.ObjectMapper;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import com.bio.bio_backend.global.dto.ApiResponseDto;
import com.bio.bio_backend.domain.base.member.service.MemberService;
import com.bio.bio_backend.global.constants.ApiResponseCode;
import com.bio.bio_backend.global.utils.JwtUtils;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@RequiredArgsConstructor
@Slf4j
public class JwtTokenValidationFilter extends OncePerRequestFilter {

    private final JwtUtils jwtUtils;
    private final HttpUtils httpUtils;
    private final MemberService memberService;



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

        log.debug("JWT 토큰 검증 필터 실행 - URI: {}", request.getRequestURI());

        String accessToken = jwtUtils.extractAccessJwtFromCookie(request);
        String refreshToken = jwtUtils.extractRefreshJwtFromCookie(request);

        // Access Token이 있고 유효한 경우
        if (accessToken != null && jwtUtils.validateAccessToken(accessToken)) {
            String username = jwtUtils.extractUsername(accessToken);
            UserDetails userDetails = jwtUtils.createUserDetailsFromToken(accessToken);

            if (userDetails != null) {
                UsernamePasswordAuthenticationToken authentication =
                        new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                SecurityContextHolder.getContext().setAuthentication(authentication);
                
                log.debug("Access Token 인증 성공: {}", username);
                filterChain.doFilter(request, response);
                return;
            }
        } else {
            // Access Token이 없거나 만료된 경우, Refresh Token으로 갱신 시도
            if (refreshToken != null) {
                // 1. Refresh Token 유효성 검증
                if (!jwtUtils.isValidRefreshToken(refreshToken)) {
                    log.warn("Refresh Token이 유효하지 않습니다. URI: {}", request.getRequestURI());
                    sendJsonResponse(response, ApiResponseDto.fail(ApiResponseCode.JWT_TOKEN_EXPIRED));
                    return;
                }

                // 2. IP 주소 검증
                if (!jwtUtils.isValidClientIp(refreshToken, httpUtils.getClientIp())) {
                    log.warn("클라이언트 IP 주소가 일치하지 않습니다. URI: {}, IP: {}",
                            request.getRequestURI(), request.getRemoteAddr());
                    sendJsonResponse(response, ApiResponseDto.fail(ApiResponseCode.INVALID_CLIENT_IP));
                    return;
                }

                // 모든 검증을 통과한 경우 토큰 갱신 진행
                String username = jwtUtils.extractUsername(refreshToken);

                UserDetails userDetails = memberService.loadUserByUsername(username);

                // 새로운 Access Token 생성
                String newAccessToken = jwtUtils.createAccessToken(
                    ((MemberDto) userDetails).getOid(),
                    userDetails.getUsername(), 
                    ((MemberDto) userDetails).getName()
                );

                // 새로운 Access Token을 쿠키에 설정
                jwtUtils.setAccessTokenCookie(response, newAccessToken);

                // Refresh Token 갱신
                String newRefreshToken = jwtUtils.createRefreshToken(username, httpUtils.getClientIp());
                jwtUtils.setRefreshTokenCookie(response, newRefreshToken);

                MemberDto member = (MemberDto) userDetails;
                member.setRefreshToken(newRefreshToken);
                member.setLoginIp(httpUtils.getClientIp());
                memberService.updateMember(member);

                // 인증 정보 설정
                UsernamePasswordAuthenticationToken authentication =
                        new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                SecurityContextHolder.getContext().setAuthentication(authentication);

                log.info("토큰 자동 갱신 성공: {}", username);
                filterChain.doFilter(request, response);
                return;
            }

        }

        // 토큰이 없거나 모두 유효하지 않은 경우
        log.warn("유효한 JWT 토큰이 없습니다. URI: {}", request.getRequestURI());
        sendJsonResponse(response, ApiResponseDto.fail(ApiResponseCode.JWT_TOKEN_NULL));
    }

    private void sendJsonResponse(HttpServletResponse response, ApiResponseDto<?> apiResponse) throws IOException {
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setCharacterEncoding("UTF-8");
        response.setStatus(apiResponse.getCode());

        ObjectMapper objectMapper = new ObjectMapper();
        String jsonResponse = objectMapper.writeValueAsString(apiResponse);
        response.getWriter().write(jsonResponse);
    }
}
