package com.spring.infra.security.provider;

import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import com.spring.infra.security.domain.UserPrincipal;
import com.spring.infra.security.error.SecurityAuthException;
import com.spring.infra.security.error.SecurityExceptionRule;

import lombok.RequiredArgsConstructor;

/**
 * 사용자 인증을 처리하는 클래스입니다.
 * 
 * <p>이 클래스는 Spring Security의 AuthenticationProvider 인터페이스를 구현하여,
 * 사용자 이름과 비밀번호를 기반으로 인증을 수행합니다.</p>
 * 
 * <p>사용자의 비밀번호는 암호화된 형태로 저장되며, 입력된 비밀번호와 비교하여
 * 인증의 유효성을 검사합니다.</p>
 * 
 * @author mindol
 * @version 1.0
 */
@Component
@RequiredArgsConstructor
public class UserAuthenticationProvider implements AuthenticationProvider {
    
    /**
     * 비밀번호 인코더.
     * 
     * <p>사용자의 비밀번호를 암호화하고 검증하는 데 사용됩니다.</p>
     */
    private final PasswordEncoder passwordEncoder;
    
    /**
     * 사용자 세부 정보를 로드하는 서비스.
     * 
     * <p>사용자 이름을 기반으로 사용자 정보를 가져오는 데 사용됩니다.</p>
     */
    private final UserDetailsService userDetailsService;

    /**
     * 인증을 수행합니다.
     * 
     * <p>주어진 인증 정보를 기반으로 사용자를 인증하고, 인증된 사용자 정보를 반환합니다.</p>
     * 
     * @param authentication 인증 요청 정보
     * @return 인증된 Authentication 객체
     * @throws AuthenticationException 인증 실패 시 발생하는 예외
     */
    @Transactional(readOnly = true)
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String username = authentication.getName();
        String password = String.valueOf(authentication.getCredentials());
        
        // 사용자 정보를 로드합니다.
        UserPrincipal user = (UserPrincipal) userDetailsService.loadUserByUsername(username);
        
        // 비밀번호가 일치하는지 확인합니다.
        if (isNotMatches(password, user.getPassword())) {
            throw new SecurityAuthException(SecurityExceptionRule.USER_NOT_PASSWORD);
        }
        
        // 인증된 사용자 정보를 반환합니다.
        return new UsernamePasswordAuthenticationToken(user, user.getPassword(), user.getAuthorities());
    }

    /**
     * 주어진 인증 클래스가 지원되는지 확인합니다.
     * 
     * @param authentication 인증 클래스
     * @return 지원되는 경우 true, 그렇지 않으면 false
     */
    @Override
    public boolean supports(Class<?> authentication) {
        return authentication.equals(UsernamePasswordAuthenticationToken.class);
    }

    /**
     * 비밀번호와 암호화된 비밀번호가 일치하지 않는지 확인합니다.
     * 
     * @param password 입력된 비밀번호
     * @param encodePassword 암호화된 비밀번호
     * @return 일치하지 않으면 true, 일치하면 false
     */
    private boolean isNotMatches(String password, String encodePassword) {
        return !passwordEncoder.matches(password, encodePassword);
    }

}
