package com.ga.api.auth;

import com.ga.common.auth.JwtTokenProvider;
import com.ga.common.exception.BizException;
import com.ga.common.exception.ErrorCode;
import com.ga.common.system.SystemConfigService;
import com.ga.common.system.SystemLogMapper;
import com.ga.common.util.SecurityUtil;
import com.ga.core.mapper.user.UserMapper;
import com.ga.core.vo.user.UserResp;
import com.ga.core.vo.user.UserStatus;
import com.ga.core.vo.user.UserVO;
import io.jsonwebtoken.Claims;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Slf4j
@Service
@RequiredArgsConstructor
public class AuthService {

    private final UserMapper userMapper;
    private final PasswordEncoder passwordEncoder;
    private final JwtTokenProvider tokenProvider;
    private final SystemConfigService configService;
    private final SystemLogMapper systemLogMapper;

    @Transactional
    public LoginResp login(LoginReq req, String ipAddress, String userAgent) {
        UserVO user = userMapper.selectByLoginId(req.getLoginId());
        int failLimit = configService.getInt("LOGIN_FAIL_LIMIT", 5);

        if (user == null) {
            saveLoginLog(null, req.getLoginId(), "FAIL", "USER_NOT_FOUND", ipAddress, userAgent);
            throw new BizException(ErrorCode.LOGIN_FAIL);
        }
        if (UserStatus.LOCKED.name().equals(user.getStatus())) {
            saveLoginLog(user.getUserId(), req.getLoginId(), "FAIL", "ACCOUNT_LOCKED", ipAddress, userAgent);
            throw new BizException(ErrorCode.ACCOUNT_LOCKED);
        }
        if (UserStatus.RETIRED.name().equals(user.getStatus())) {
            throw new BizException(ErrorCode.ACCOUNT_RETIRED);
        }
        if (!passwordEncoder.matches(req.getPassword(), user.getPassword())) {
            userMapper.incrementFailCount(user.getUserId());
            int newFail = (user.getFailCount() == null ? 0 : user.getFailCount()) + 1;
            if (newFail >= failLimit) {
                userMapper.updateStatus(user.getUserId(), UserStatus.LOCKED.name());
            }
            saveLoginLog(user.getUserId(), req.getLoginId(), "FAIL", "BAD_PASSWORD", ipAddress, userAgent);
            throw new BizException(ErrorCode.LOGIN_FAIL);
        }

        // 로그인 성공
        userMapper.updateLastLogin(user.getUserId());
        List<String> roles = userMapper.selectRoleCodes(user.getUserId());
        String access = tokenProvider.createAccessToken(user.getUserId(), user.getLoginId(), roles);
        String refresh = tokenProvider.createRefreshToken(user.getUserId(), user.getLoginId());
        saveLoginLog(user.getUserId(), req.getLoginId(), "SUCCESS", null, ipAddress, userAgent);

        return LoginResp.builder()
                .accessToken(access)
                .refreshToken(refresh)
                .accessTokenExpiresIn(tokenProvider.getAccessExpire())
                .userId(user.getUserId())
                .loginId(user.getLoginId())
                .userName(user.getUserName())
                .roles(roles)
                .build();
    }

    public LoginResp refresh(String refreshToken) {
        Claims claims = tokenProvider.parse(refreshToken);
        if (!"REFRESH".equals(claims.get("type"))) {
            throw new BizException(ErrorCode.TOKEN_INVALID);
        }
        Long userId = claims.get("uid", Long.class);
        UserVO user = userMapper.selectById(userId);
        if (user == null || !UserStatus.ACTIVE.name().equals(user.getStatus())) {
            throw new BizException(ErrorCode.UNAUTHORIZED);
        }
        List<String> roles = userMapper.selectRoleCodes(userId);
        String newAccess = tokenProvider.createAccessToken(userId, user.getLoginId(), roles);
        return LoginResp.builder()
                .accessToken(newAccess)
                .accessTokenExpiresIn(tokenProvider.getAccessExpire())
                .userId(userId)
                .loginId(user.getLoginId())
                .userName(user.getUserName())
                .roles(roles)
                .build();
    }

    public UserResp me() {
        Long userId = SecurityUtil.getCurrentUserId();
        if (userId == null) throw new BizException(ErrorCode.UNAUTHORIZED);
        UserResp resp = userMapper.selectDetailById(userId);
        if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
        resp.setRoleCodes(userMapper.selectRoleCodes(userId));
        return resp;
    }

    @Transactional
    public void changePassword(String oldPassword, String newPassword) {
        Long userId = SecurityUtil.getCurrentUserId();
        if (userId == null) throw new BizException(ErrorCode.UNAUTHORIZED);
        UserVO user = userMapper.selectById(userId);
        if (!passwordEncoder.matches(oldPassword, user.getPassword())) {
            throw new BizException(ErrorCode.LOGIN_FAIL, "기존 비밀번호가 일치하지 않습니다");
        }
        validatePasswordPolicy(newPassword);
        userMapper.updatePassword(userId, passwordEncoder.encode(newPassword));
    }

    public void logout() {
        Long userId = SecurityUtil.getCurrentUserId();
        saveLoginLog(userId, SecurityUtil.getCurrentLoginId(), "LOGOUT", null, null, null);
    }

    private void saveLoginLog(Long userId, String loginId, String type, String reason, String ip, String ua) {
        try {
            systemLogMapper.insertLoginLog(userId, loginId, type, reason, ip, ua);
        } catch (Exception e) {
            log.warn("Login log save failed: {}", e.getMessage());
        }
    }

    private void validatePasswordPolicy(String password) {
        int min = configService.getInt("PASSWORD_MIN_LEN", 8);
        if (password == null || password.length() < min) {
            throw new BizException(ErrorCode.PASSWORD_POLICY,
                    "비밀번호는 " + min + "자 이상이어야 합니다");
        }
    }
}
