package com.caliverse.admin.domain.service;

import com.caliverse.admin.domain.dao.admin.AdminMapper;
import com.caliverse.admin.domain.dao.admin.TokenMapper;
import com.caliverse.admin.domain.entity.Admin;
import com.caliverse.admin.domain.entity.STATUS;
import com.caliverse.admin.domain.entity.Token;
import com.caliverse.admin.domain.request.AuthenticateRequest;
import com.caliverse.admin.domain.response.AuthenticateResponse;
import com.caliverse.admin.global.common.code.CommonCode;
import com.caliverse.admin.global.common.code.ErrorCode;
import com.caliverse.admin.global.common.code.SuccessCode;
import com.caliverse.admin.global.common.exception.RestApiException;
import com.caliverse.admin.global.common.utils.CommonUtils;
import com.caliverse.admin.global.configuration.JwtService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;

@Slf4j
@Service
@RequiredArgsConstructor
public class AuthenticateService{

    private final AdminMapper userMapper;
    private final TokenMapper tokenMapper;
    private final AuthenticationManager authenticationManager;
    private final JwtService jwtService;
    private final PasswordEncoder passwordEncoder;
    private final HistoryService historyService;
    @Value("${password.expiration-days}")
    private int passwordExpiration;

    // 로그인
    public AuthenticateResponse login(AuthenticateRequest authenticateRequest) {

        String email = authenticateRequest.getEmail();
        String pw = authenticateRequest.getPassword();
        // 삭제 계정 여부
        if(!userMapper.existsByEmail(email)){
            throw new RestApiException(CommonCode.ERROR.getHttpStatus(), ErrorCode.NOT_MATCH_USER.getMessage());
        }

        try {
            authenticationManager.authenticate(new UsernamePasswordAuthenticationToken( email, pw ));
        }catch (AuthenticationException authException){
            // 인증에 실패하면 예외가 발생 AuthenticationException 떨어짐
            throw new RestApiException(CommonCode.ERROR.getHttpStatus(), ErrorCode.NOT_FOUND_USER.getMessage());
        }

        Admin admin = userMapper.findByEmail(authenticateRequest.getEmail())
                .orElseThrow(() -> new RestApiException(CommonCode.ERROR.getHttpStatus(), ErrorCode.NOT_MATCH_USER.getMessage()));

        //비번 기간 만료
        if (isPasswordExpired(admin.getPwUpdateDt())){
            throw new RestApiException(CommonCode.ERROR.getHttpStatus(), ErrorCode.PWD_EXPIRATION.getMessage()
                    +"("+admin.getPwUpdateDt().plus(passwordExpiration,ChronoUnit.DAYS)
                    .atZone(ZoneId.of("UTC"))
                    .withZoneSameInstant(ZoneId.of("Asia/Seoul"))
                    .format(DateTimeFormatter.ofPattern("yyyy-MM-dd"))+" 종료)");
        }

        if(admin.getStatus().name().equals("ROLE_NOT_PERMITTED") || admin.getStatus().name().equals("REJECT")){
            throw new RestApiException(CommonCode.ERROR.getHttpStatus(), ErrorCode.NOT_PERMITTED.getMessage());
        }
        int tokenCnt = tokenMapper.getCount(admin.getId());
        if(tokenCnt > 1){
            tokenMapper.updateResetToken(admin.getId());
        }

        var jwtToken = jwtService.generateToken(admin);
        var refreshToken = jwtService.generateRefreshToken(admin);
        revokeAllUserTokens(admin);
        saveUserToken(admin.getId(), jwtToken);

        log.info("login id: {}, token: {}", admin.getId(), jwtToken);

        return AuthenticateResponse.builder()
                .status(CommonCode.SUCCESS.getHttpStatus())
                .result(CommonCode.SUCCESS.getResult())
                .authValue(new AuthenticateResponse.AuthValue(jwtToken, refreshToken,String.valueOf(admin.getStatus())))
                .build();
    }

    //회원가입
    public AuthenticateResponse register(AuthenticateRequest authenticateRequest){

        if(userMapper.existsByEmail(authenticateRequest.getEmail())){
            throw new RestApiException(CommonCode.ERROR.getHttpStatus(), ErrorCode.DUPLICATED_EMAIL.getMessage());
        }

        Admin admin = Admin.builder()
                // .status(STATUS.ROLE_NOT_PERMITTED) //2024.08.02 즉시 회원가입되게 변경
                .groupId((long)2)
                .status(STATUS.PERMITTED)
                .name(authenticateRequest.getName())
                .email(authenticateRequest.getEmail())
                .password(passwordEncoder.encode(authenticateRequest.getPassword()))
                .build();

        userMapper.save(admin);
        log.info("register Sign Up : {}", admin);
        return AuthenticateResponse.builder()
                .status(CommonCode.SUCCESS.getHttpStatus())
                .result(CommonCode.SUCCESS.getResult())
                .authValue(AuthenticateResponse.AuthValue.builder().message(SuccessCode.SAVE.getMessage()).build())
                .build();
    }

    // 토큰 저장
    private void saveUserToken(Long memberId, String jwtToken) {
        var token = Token.builder()
                .adminId(memberId)
                .token(jwtToken)
                .tokenType(Token.TokenType.BEARER)
                .expired(false)
                .revoked(false)
                .build();
        tokenMapper.save(token);
    }

    //토큰 삭제
    private void revokeAllUserTokens(Admin member) {
        var validUserTokens = tokenMapper.findAllValidTokenByUser(member.getId()).orElse(null);
        if(validUserTokens!=null){
            validUserTokens.setExpired(true);
            validUserTokens.setRevoked(true);
            tokenMapper.updateToken(validUserTokens);
        }
    }

    public boolean isPasswordExpired(LocalDateTime lastPasswordChangeDateTime) {
        LocalDateTime currentDateTime = LocalDateTime.now();
        LocalDateTime minusDate = currentDateTime.minusDays(passwordExpiration);

        boolean isAfter = minusDate.isAfter(lastPasswordChangeDateTime);
        
        return isAfter;
    }
}
